blob aa6c2dea (326437B) - Raw
1 const std = @import("std"); 2 const builtin = @import("builtin"); 3 const Allocator = std.mem.Allocator; 4 const ArrayList = std.ArrayList; 5 const assert = std.debug.assert; 6 const testing = std.testing; 7 const leb = std.leb; 8 const mem = std.mem; 9 const wasm = std.wasm; 10 const log = std.log.scoped(.codegen); 11 12 const codegen = @import("../../codegen.zig"); 13 const Module = @import("../../Module.zig"); 14 const InternPool = @import("../../InternPool.zig"); 15 const Decl = Module.Decl; 16 const Type = @import("../../type.zig").Type; 17 const Value = @import("../../value.zig").Value; 18 const Compilation = @import("../../Compilation.zig"); 19 const LazySrcLoc = Module.LazySrcLoc; 20 const link = @import("../../link.zig"); 21 const TypedValue = @import("../../TypedValue.zig"); 22 const Air = @import("../../Air.zig"); 23 const Liveness = @import("../../Liveness.zig"); 24 const target_util = @import("../../target.zig"); 25 const Mir = @import("Mir.zig"); 26 const Emit = @import("Emit.zig"); 27 const abi = @import("abi.zig"); 28 const Alignment = InternPool.Alignment; 29 const errUnionPayloadOffset = codegen.errUnionPayloadOffset; 30 const errUnionErrorOffset = codegen.errUnionErrorOffset; 31 32 /// Wasm Value, created when generating an instruction 33 const WValue = union(enum) { 34 /// `WValue` which has been freed and may no longer hold 35 /// any references. 36 dead: void, 37 /// May be referenced but is unused 38 none: void, 39 /// The value lives on top of the stack 40 stack: void, 41 /// Index of the local 42 local: struct { 43 /// Contains the index to the local 44 value: u32, 45 /// The amount of instructions referencing this `WValue` 46 references: u32, 47 }, 48 /// An immediate 32bit value 49 imm32: u32, 50 /// An immediate 64bit value 51 imm64: u64, 52 /// Index into the list of simd128 immediates. This `WValue` is 53 /// only possible in very rare cases, therefore it would be 54 /// a waste of memory to store the value in a 128 bit integer. 55 imm128: u32, 56 /// A constant 32bit float value 57 float32: f32, 58 /// A constant 64bit float value 59 float64: f64, 60 /// A value that represents a pointer to the data section 61 /// Note: The value contains the symbol index, rather than the actual address 62 /// as we use this to perform the relocation. 63 memory: u32, 64 /// A value that represents a parent pointer and an offset 65 /// from that pointer. i.e. when slicing with constant values. 66 memory_offset: struct { 67 /// The symbol of the parent pointer 68 pointer: u32, 69 /// Offset will be set as addend when relocating 70 offset: u32, 71 }, 72 /// Represents a function pointer 73 /// In wasm function pointers are indexes into a function table, 74 /// rather than an address in the data section. 75 function_index: u32, 76 /// Offset from the bottom of the virtual stack, with the offset 77 /// pointing to where the value lives. 78 stack_offset: struct { 79 /// Contains the actual value of the offset 80 value: u32, 81 /// The amount of instructions referencing this `WValue` 82 references: u32, 83 }, 84 85 /// Returns the offset from the bottom of the stack. This is useful when 86 /// we use the load or store instruction to ensure we retrieve the value 87 /// from the correct position, rather than the value that lives at the 88 /// bottom of the stack. For instances where `WValue` is not `stack_value` 89 /// this will return 0, which allows us to simply call this function for all 90 /// loads and stores without requiring checks everywhere. 91 fn offset(value: WValue) u32 { 92 switch (value) { 93 .stack_offset => |stack_offset| return stack_offset.value, 94 .dead => unreachable, 95 else => return 0, 96 } 97 } 98 99 /// Promotes a `WValue` to a local when given value is on top of the stack. 100 /// When encountering a `local` or `stack_offset` this is essentially a no-op. 101 /// All other tags are illegal. 102 fn toLocal(value: WValue, gen: *CodeGen, ty: Type) InnerError!WValue { 103 switch (value) { 104 .stack => { 105 const new_local = try gen.allocLocal(ty); 106 try gen.addLabel(.local_set, new_local.local.value); 107 return new_local; 108 }, 109 .local, .stack_offset => return value, 110 else => unreachable, 111 } 112 } 113 114 /// Marks a local as no longer being referenced and essentially allows 115 /// us to re-use it somewhere else within the function. 116 /// The valtype of the local is deducted by using the index of the given `WValue`. 117 fn free(value: *WValue, gen: *CodeGen) void { 118 if (value.* != .local) return; 119 const local_value = value.local.value; 120 const reserved = gen.args.len + @intFromBool(gen.return_value != .none); 121 if (local_value < reserved + 2) return; // reserved locals may never be re-used. Also accounts for 2 stack locals. 122 123 const index = local_value - reserved; 124 const valtype = @as(wasm.Valtype, @enumFromInt(gen.locals.items[index])); 125 switch (valtype) { 126 .i32 => gen.free_locals_i32.append(gen.gpa, local_value) catch return, // It's ok to fail any of those, a new local can be allocated instead 127 .i64 => gen.free_locals_i64.append(gen.gpa, local_value) catch return, 128 .f32 => gen.free_locals_f32.append(gen.gpa, local_value) catch return, 129 .f64 => gen.free_locals_f64.append(gen.gpa, local_value) catch return, 130 .v128 => gen.free_locals_v128.append(gen.gpa, local_value) catch return, 131 } 132 log.debug("freed local ({d}) of type {}", .{ local_value, valtype }); 133 value.* = .dead; 134 } 135 }; 136 137 /// Wasm ops, but without input/output/signedness information 138 /// Used for `buildOpcode` 139 const Op = enum { 140 @"unreachable", 141 nop, 142 block, 143 loop, 144 @"if", 145 @"else", 146 end, 147 br, 148 br_if, 149 br_table, 150 @"return", 151 call, 152 call_indirect, 153 drop, 154 select, 155 local_get, 156 local_set, 157 local_tee, 158 global_get, 159 global_set, 160 load, 161 store, 162 memory_size, 163 memory_grow, 164 @"const", 165 eqz, 166 eq, 167 ne, 168 lt, 169 gt, 170 le, 171 ge, 172 clz, 173 ctz, 174 popcnt, 175 add, 176 sub, 177 mul, 178 div, 179 rem, 180 @"and", 181 @"or", 182 xor, 183 shl, 184 shr, 185 rotl, 186 rotr, 187 abs, 188 neg, 189 ceil, 190 floor, 191 trunc, 192 nearest, 193 sqrt, 194 min, 195 max, 196 copysign, 197 wrap, 198 convert, 199 demote, 200 promote, 201 reinterpret, 202 extend, 203 }; 204 205 /// Contains the settings needed to create an `Opcode` using `buildOpcode`. 206 /// 207 /// The fields correspond to the opcode name. Here is an example 208 /// i32_trunc_f32_s 209 /// ^ ^ ^ ^ 210 /// | | | | 211 /// valtype1 | | | 212 /// = .i32 | | | 213 /// | | | 214 /// op | | 215 /// = .trunc | | 216 /// | | 217 /// valtype2 | 218 /// = .f32 | 219 /// | 220 /// width | 221 /// = null | 222 /// | 223 /// signed 224 /// = true 225 /// 226 /// There can be missing fields, here are some more examples: 227 /// i64_load8_u 228 /// --> .{ .valtype1 = .i64, .op = .load, .width = 8, signed = false } 229 /// i32_mul 230 /// --> .{ .valtype1 = .i32, .op = .trunc } 231 /// nop 232 /// --> .{ .op = .nop } 233 const OpcodeBuildArguments = struct { 234 /// First valtype in the opcode (usually represents the type of the output) 235 valtype1: ?wasm.Valtype = null, 236 /// The operation (e.g. call, unreachable, div, min, sqrt, etc.) 237 op: Op, 238 /// Width of the operation (e.g. 8 for i32_load8_s, 16 for i64_extend16_i32_s) 239 width: ?u8 = null, 240 /// Second valtype in the opcode name (usually represents the type of the input) 241 valtype2: ?wasm.Valtype = null, 242 /// Signedness of the op 243 signedness: ?std.builtin.Signedness = null, 244 }; 245 246 /// Helper function that builds an Opcode given the arguments needed 247 fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { 248 switch (args.op) { 249 .@"unreachable" => return .@"unreachable", 250 .nop => return .nop, 251 .block => return .block, 252 .loop => return .loop, 253 .@"if" => return .@"if", 254 .@"else" => return .@"else", 255 .end => return .end, 256 .br => return .br, 257 .br_if => return .br_if, 258 .br_table => return .br_table, 259 .@"return" => return .@"return", 260 .call => return .call, 261 .call_indirect => return .call_indirect, 262 .drop => return .drop, 263 .select => return .select, 264 .local_get => return .local_get, 265 .local_set => return .local_set, 266 .local_tee => return .local_tee, 267 .global_get => return .global_get, 268 .global_set => return .global_set, 269 270 .load => if (args.width) |width| switch (width) { 271 8 => switch (args.valtype1.?) { 272 .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u, 273 .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u, 274 .f32, .f64, .v128 => unreachable, 275 }, 276 16 => switch (args.valtype1.?) { 277 .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u, 278 .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u, 279 .f32, .f64, .v128 => unreachable, 280 }, 281 32 => switch (args.valtype1.?) { 282 .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u, 283 .i32 => return .i32_load, 284 .f32 => return .f32_load, 285 .f64, .v128 => unreachable, 286 }, 287 64 => switch (args.valtype1.?) { 288 .i64 => return .i64_load, 289 .f64 => return .f64_load, 290 else => unreachable, 291 }, 292 else => unreachable, 293 } else switch (args.valtype1.?) { 294 .i32 => return .i32_load, 295 .i64 => return .i64_load, 296 .f32 => return .f32_load, 297 .f64 => return .f64_load, 298 .v128 => unreachable, // handled independently 299 }, 300 .store => if (args.width) |width| { 301 switch (width) { 302 8 => switch (args.valtype1.?) { 303 .i32 => return .i32_store8, 304 .i64 => return .i64_store8, 305 .f32, .f64, .v128 => unreachable, 306 }, 307 16 => switch (args.valtype1.?) { 308 .i32 => return .i32_store16, 309 .i64 => return .i64_store16, 310 .f32, .f64, .v128 => unreachable, 311 }, 312 32 => switch (args.valtype1.?) { 313 .i64 => return .i64_store32, 314 .i32 => return .i32_store, 315 .f32 => return .f32_store, 316 .f64, .v128 => unreachable, 317 }, 318 64 => switch (args.valtype1.?) { 319 .i64 => return .i64_store, 320 .f64 => return .f64_store, 321 else => unreachable, 322 }, 323 else => unreachable, 324 } 325 } else { 326 switch (args.valtype1.?) { 327 .i32 => return .i32_store, 328 .i64 => return .i64_store, 329 .f32 => return .f32_store, 330 .f64 => return .f64_store, 331 .v128 => unreachable, // handled independently 332 } 333 }, 334 335 .memory_size => return .memory_size, 336 .memory_grow => return .memory_grow, 337 338 .@"const" => switch (args.valtype1.?) { 339 .i32 => return .i32_const, 340 .i64 => return .i64_const, 341 .f32 => return .f32_const, 342 .f64 => return .f64_const, 343 .v128 => unreachable, // handled independently 344 }, 345 346 .eqz => switch (args.valtype1.?) { 347 .i32 => return .i32_eqz, 348 .i64 => return .i64_eqz, 349 .f32, .f64, .v128 => unreachable, 350 }, 351 .eq => switch (args.valtype1.?) { 352 .i32 => return .i32_eq, 353 .i64 => return .i64_eq, 354 .f32 => return .f32_eq, 355 .f64 => return .f64_eq, 356 .v128 => unreachable, // handled independently 357 }, 358 .ne => switch (args.valtype1.?) { 359 .i32 => return .i32_ne, 360 .i64 => return .i64_ne, 361 .f32 => return .f32_ne, 362 .f64 => return .f64_ne, 363 .v128 => unreachable, // handled independently 364 }, 365 366 .lt => switch (args.valtype1.?) { 367 .i32 => if (args.signedness.? == .signed) return .i32_lt_s else return .i32_lt_u, 368 .i64 => if (args.signedness.? == .signed) return .i64_lt_s else return .i64_lt_u, 369 .f32 => return .f32_lt, 370 .f64 => return .f64_lt, 371 .v128 => unreachable, // handled independently 372 }, 373 .gt => switch (args.valtype1.?) { 374 .i32 => if (args.signedness.? == .signed) return .i32_gt_s else return .i32_gt_u, 375 .i64 => if (args.signedness.? == .signed) return .i64_gt_s else return .i64_gt_u, 376 .f32 => return .f32_gt, 377 .f64 => return .f64_gt, 378 .v128 => unreachable, // handled independently 379 }, 380 .le => switch (args.valtype1.?) { 381 .i32 => if (args.signedness.? == .signed) return .i32_le_s else return .i32_le_u, 382 .i64 => if (args.signedness.? == .signed) return .i64_le_s else return .i64_le_u, 383 .f32 => return .f32_le, 384 .f64 => return .f64_le, 385 .v128 => unreachable, // handled independently 386 }, 387 .ge => switch (args.valtype1.?) { 388 .i32 => if (args.signedness.? == .signed) return .i32_ge_s else return .i32_ge_u, 389 .i64 => if (args.signedness.? == .signed) return .i64_ge_s else return .i64_ge_u, 390 .f32 => return .f32_ge, 391 .f64 => return .f64_ge, 392 .v128 => unreachable, // handled independently 393 }, 394 395 .clz => switch (args.valtype1.?) { 396 .i32 => return .i32_clz, 397 .i64 => return .i64_clz, 398 .f32, .f64 => unreachable, 399 .v128 => unreachable, // handled independently 400 }, 401 .ctz => switch (args.valtype1.?) { 402 .i32 => return .i32_ctz, 403 .i64 => return .i64_ctz, 404 .f32, .f64 => unreachable, 405 .v128 => unreachable, // handled independently 406 }, 407 .popcnt => switch (args.valtype1.?) { 408 .i32 => return .i32_popcnt, 409 .i64 => return .i64_popcnt, 410 .f32, .f64 => unreachable, 411 .v128 => unreachable, // handled independently 412 }, 413 414 .add => switch (args.valtype1.?) { 415 .i32 => return .i32_add, 416 .i64 => return .i64_add, 417 .f32 => return .f32_add, 418 .f64 => return .f64_add, 419 .v128 => unreachable, // handled independently 420 }, 421 .sub => switch (args.valtype1.?) { 422 .i32 => return .i32_sub, 423 .i64 => return .i64_sub, 424 .f32 => return .f32_sub, 425 .f64 => return .f64_sub, 426 .v128 => unreachable, // handled independently 427 }, 428 .mul => switch (args.valtype1.?) { 429 .i32 => return .i32_mul, 430 .i64 => return .i64_mul, 431 .f32 => return .f32_mul, 432 .f64 => return .f64_mul, 433 .v128 => unreachable, // handled independently 434 }, 435 436 .div => switch (args.valtype1.?) { 437 .i32 => if (args.signedness.? == .signed) return .i32_div_s else return .i32_div_u, 438 .i64 => if (args.signedness.? == .signed) return .i64_div_s else return .i64_div_u, 439 .f32 => return .f32_div, 440 .f64 => return .f64_div, 441 .v128 => unreachable, // handled independently 442 }, 443 .rem => switch (args.valtype1.?) { 444 .i32 => if (args.signedness.? == .signed) return .i32_rem_s else return .i32_rem_u, 445 .i64 => if (args.signedness.? == .signed) return .i64_rem_s else return .i64_rem_u, 446 .f32, .f64 => unreachable, 447 .v128 => unreachable, // handled independently 448 }, 449 450 .@"and" => switch (args.valtype1.?) { 451 .i32 => return .i32_and, 452 .i64 => return .i64_and, 453 .f32, .f64 => unreachable, 454 .v128 => unreachable, // handled independently 455 }, 456 .@"or" => switch (args.valtype1.?) { 457 .i32 => return .i32_or, 458 .i64 => return .i64_or, 459 .f32, .f64 => unreachable, 460 .v128 => unreachable, // handled independently 461 }, 462 .xor => switch (args.valtype1.?) { 463 .i32 => return .i32_xor, 464 .i64 => return .i64_xor, 465 .f32, .f64 => unreachable, 466 .v128 => unreachable, // handled independently 467 }, 468 469 .shl => switch (args.valtype1.?) { 470 .i32 => return .i32_shl, 471 .i64 => return .i64_shl, 472 .f32, .f64 => unreachable, 473 .v128 => unreachable, // handled independently 474 }, 475 .shr => switch (args.valtype1.?) { 476 .i32 => if (args.signedness.? == .signed) return .i32_shr_s else return .i32_shr_u, 477 .i64 => if (args.signedness.? == .signed) return .i64_shr_s else return .i64_shr_u, 478 .f32, .f64 => unreachable, 479 .v128 => unreachable, // handled independently 480 }, 481 .rotl => switch (args.valtype1.?) { 482 .i32 => return .i32_rotl, 483 .i64 => return .i64_rotl, 484 .f32, .f64 => unreachable, 485 .v128 => unreachable, // handled independently 486 }, 487 .rotr => switch (args.valtype1.?) { 488 .i32 => return .i32_rotr, 489 .i64 => return .i64_rotr, 490 .f32, .f64 => unreachable, 491 .v128 => unreachable, // handled independently 492 }, 493 494 .abs => switch (args.valtype1.?) { 495 .i32, .i64 => unreachable, 496 .f32 => return .f32_abs, 497 .f64 => return .f64_abs, 498 .v128 => unreachable, // handled independently 499 }, 500 .neg => switch (args.valtype1.?) { 501 .i32, .i64 => unreachable, 502 .f32 => return .f32_neg, 503 .f64 => return .f64_neg, 504 .v128 => unreachable, // handled independently 505 }, 506 .ceil => switch (args.valtype1.?) { 507 .i64 => unreachable, 508 .i32 => return .f32_ceil, // when valtype is f16, we store it in i32. 509 .f32 => return .f32_ceil, 510 .f64 => return .f64_ceil, 511 .v128 => unreachable, // handled independently 512 }, 513 .floor => switch (args.valtype1.?) { 514 .i64 => unreachable, 515 .i32 => return .f32_floor, // when valtype is f16, we store it in i32. 516 .f32 => return .f32_floor, 517 .f64 => return .f64_floor, 518 .v128 => unreachable, // handled independently 519 }, 520 .trunc => switch (args.valtype1.?) { 521 .i32 => if (args.valtype2) |valty| switch (valty) { 522 .i32 => unreachable, 523 .i64 => unreachable, 524 .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u, 525 .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u, 526 .v128 => unreachable, // handled independently 527 } else return .f32_trunc, // when no valtype2, it's an f16 instead which is stored in an i32. 528 .i64 => switch (args.valtype2.?) { 529 .i32 => unreachable, 530 .i64 => unreachable, 531 .f32 => if (args.signedness.? == .signed) return .i64_trunc_f32_s else return .i64_trunc_f32_u, 532 .f64 => if (args.signedness.? == .signed) return .i64_trunc_f64_s else return .i64_trunc_f64_u, 533 .v128 => unreachable, // handled independently 534 }, 535 .f32 => return .f32_trunc, 536 .f64 => return .f64_trunc, 537 .v128 => unreachable, // handled independently 538 }, 539 .nearest => switch (args.valtype1.?) { 540 .i32, .i64 => unreachable, 541 .f32 => return .f32_nearest, 542 .f64 => return .f64_nearest, 543 .v128 => unreachable, // handled independently 544 }, 545 .sqrt => switch (args.valtype1.?) { 546 .i32, .i64 => unreachable, 547 .f32 => return .f32_sqrt, 548 .f64 => return .f64_sqrt, 549 .v128 => unreachable, // handled independently 550 }, 551 .min => switch (args.valtype1.?) { 552 .i32, .i64 => unreachable, 553 .f32 => return .f32_min, 554 .f64 => return .f64_min, 555 .v128 => unreachable, // handled independently 556 }, 557 .max => switch (args.valtype1.?) { 558 .i32, .i64 => unreachable, 559 .f32 => return .f32_max, 560 .f64 => return .f64_max, 561 .v128 => unreachable, // handled independently 562 }, 563 .copysign => switch (args.valtype1.?) { 564 .i32, .i64 => unreachable, 565 .f32 => return .f32_copysign, 566 .f64 => return .f64_copysign, 567 .v128 => unreachable, // handled independently 568 }, 569 570 .wrap => switch (args.valtype1.?) { 571 .i32 => switch (args.valtype2.?) { 572 .i32 => unreachable, 573 .i64 => return .i32_wrap_i64, 574 .f32, .f64 => unreachable, 575 .v128 => unreachable, // handled independently 576 }, 577 .i64, .f32, .f64 => unreachable, 578 .v128 => unreachable, // handled independently 579 }, 580 .convert => switch (args.valtype1.?) { 581 .i32, .i64 => unreachable, 582 .f32 => switch (args.valtype2.?) { 583 .i32 => if (args.signedness.? == .signed) return .f32_convert_i32_s else return .f32_convert_i32_u, 584 .i64 => if (args.signedness.? == .signed) return .f32_convert_i64_s else return .f32_convert_i64_u, 585 .f32, .f64 => unreachable, 586 .v128 => unreachable, // handled independently 587 }, 588 .f64 => switch (args.valtype2.?) { 589 .i32 => if (args.signedness.? == .signed) return .f64_convert_i32_s else return .f64_convert_i32_u, 590 .i64 => if (args.signedness.? == .signed) return .f64_convert_i64_s else return .f64_convert_i64_u, 591 .f32, .f64 => unreachable, 592 .v128 => unreachable, // handled independently 593 }, 594 .v128 => unreachable, // handled independently 595 }, 596 .demote => if (args.valtype1.? == .f32 and args.valtype2.? == .f64) return .f32_demote_f64 else unreachable, 597 .promote => if (args.valtype1.? == .f64 and args.valtype2.? == .f32) return .f64_promote_f32 else unreachable, 598 .reinterpret => switch (args.valtype1.?) { 599 .i32 => if (args.valtype2.? == .f32) return .i32_reinterpret_f32 else unreachable, 600 .i64 => if (args.valtype2.? == .f64) return .i64_reinterpret_f64 else unreachable, 601 .f32 => if (args.valtype2.? == .i32) return .f32_reinterpret_i32 else unreachable, 602 .f64 => if (args.valtype2.? == .i64) return .f64_reinterpret_i64 else unreachable, 603 .v128 => unreachable, // handled independently 604 }, 605 .extend => switch (args.valtype1.?) { 606 .i32 => switch (args.width.?) { 607 8 => if (args.signedness.? == .signed) return .i32_extend8_s else unreachable, 608 16 => if (args.signedness.? == .signed) return .i32_extend16_s else unreachable, 609 else => unreachable, 610 }, 611 .i64 => switch (args.width.?) { 612 8 => if (args.signedness.? == .signed) return .i64_extend8_s else unreachable, 613 16 => if (args.signedness.? == .signed) return .i64_extend16_s else unreachable, 614 32 => if (args.signedness.? == .signed) return .i64_extend32_s else unreachable, 615 else => unreachable, 616 }, 617 .f32, .f64 => unreachable, 618 .v128 => unreachable, // handled independently 619 }, 620 } 621 } 622 623 test "Wasm - buildOpcode" { 624 // Make sure buildOpcode is referenced, and test some examples 625 const i32_const = buildOpcode(.{ .op = .@"const", .valtype1 = .i32 }); 626 const end = buildOpcode(.{ .op = .end }); 627 const local_get = buildOpcode(.{ .op = .local_get }); 628 const i64_extend32_s = buildOpcode(.{ .op = .extend, .valtype1 = .i64, .width = 32, .signedness = .signed }); 629 const f64_reinterpret_i64 = buildOpcode(.{ .op = .reinterpret, .valtype1 = .f64, .valtype2 = .i64 }); 630 631 try testing.expectEqual(@as(wasm.Opcode, .i32_const), i32_const); 632 try testing.expectEqual(@as(wasm.Opcode, .end), end); 633 try testing.expectEqual(@as(wasm.Opcode, .local_get), local_get); 634 try testing.expectEqual(@as(wasm.Opcode, .i64_extend32_s), i64_extend32_s); 635 try testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64); 636 } 637 638 /// Hashmap to store generated `WValue` for each `Air.Inst.Ref` 639 pub const ValueTable = std.AutoArrayHashMapUnmanaged(Air.Inst.Ref, WValue); 640 641 const CodeGen = @This(); 642 643 /// Reference to the function declaration the code 644 /// section belongs to 645 decl: *Decl, 646 decl_index: InternPool.DeclIndex, 647 /// Current block depth. Used to calculate the relative difference between a break 648 /// and block 649 block_depth: u32 = 0, 650 air: Air, 651 liveness: Liveness, 652 gpa: mem.Allocator, 653 debug_output: codegen.DebugInfoOutput, 654 func_index: InternPool.Index, 655 /// Contains a list of current branches. 656 /// When we return from a branch, the branch will be popped from this list, 657 /// which means branches can only contain references from within its own branch, 658 /// or a branch higher (lower index) in the tree. 659 branches: std.ArrayListUnmanaged(Branch) = .{}, 660 /// Table to save `WValue`'s generated by an `Air.Inst` 661 // values: ValueTable, 662 /// Mapping from Air.Inst.Index to block ids 663 blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct { 664 label: u32, 665 value: WValue, 666 }) = .{}, 667 /// `bytes` contains the wasm bytecode belonging to the 'code' section. 668 code: *ArrayList(u8), 669 /// The index the next local generated will have 670 /// NOTE: arguments share the index with locals therefore the first variable 671 /// will have the index that comes after the last argument's index 672 local_index: u32 = 0, 673 /// The index of the current argument. 674 /// Used to track which argument is being referenced in `airArg`. 675 arg_index: u32 = 0, 676 /// If codegen fails, an error messages will be allocated and saved in `err_msg` 677 err_msg: *Module.ErrorMsg, 678 /// List of all locals' types generated throughout this declaration 679 /// used to emit locals count at start of 'code' section. 680 locals: std.ArrayListUnmanaged(u8), 681 /// List of simd128 immediates. Each value is stored as an array of bytes. 682 /// This list will only be populated for 128bit-simd values when the target features 683 /// are enabled also. 684 simd_immediates: std.ArrayListUnmanaged([16]u8) = .{}, 685 /// The Target we're emitting (used to call intInfo) 686 target: std.Target, 687 /// Represents the wasm binary file that is being linked. 688 bin_file: *link.File.Wasm, 689 /// List of MIR Instructions 690 mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, 691 /// Contains extra data for MIR 692 mir_extra: std.ArrayListUnmanaged(u32) = .{}, 693 /// When a function is executing, we store the the current stack pointer's value within this local. 694 /// This value is then used to restore the stack pointer to the original value at the return of the function. 695 initial_stack_value: WValue = .none, 696 /// The current stack pointer substracted with the stack size. From this value, we will calculate 697 /// all offsets of the stack values. 698 bottom_stack_value: WValue = .none, 699 /// Arguments of this function declaration 700 /// This will be set after `resolveCallingConventionValues` 701 args: []WValue = &.{}, 702 /// This will only be `.none` if the function returns void, or returns an immediate. 703 /// When it returns a pointer to the stack, the `.local` tag will be active and must be populated 704 /// before this function returns its execution to the caller. 705 return_value: WValue = .none, 706 /// The size of the stack this function occupies. In the function prologue 707 /// we will move the stack pointer by this number, forward aligned with the `stack_alignment`. 708 stack_size: u32 = 0, 709 /// The stack alignment, which is 16 bytes by default. This is specified by the 710 /// tool-conventions: https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md 711 /// and also what the llvm backend will emit. 712 /// However, local variables or the usage of `@setAlignStack` can overwrite this default. 713 stack_alignment: Alignment = .@"16", 714 715 // For each individual Wasm valtype we store a seperate free list which 716 // allows us to re-use locals that are no longer used. e.g. a temporary local. 717 /// A list of indexes which represents a local of valtype `i32`. 718 /// It is illegal to store a non-i32 valtype in this list. 719 free_locals_i32: std.ArrayListUnmanaged(u32) = .{}, 720 /// A list of indexes which represents a local of valtype `i64`. 721 /// It is illegal to store a non-i64 valtype in this list. 722 free_locals_i64: std.ArrayListUnmanaged(u32) = .{}, 723 /// A list of indexes which represents a local of valtype `f32`. 724 /// It is illegal to store a non-f32 valtype in this list. 725 free_locals_f32: std.ArrayListUnmanaged(u32) = .{}, 726 /// A list of indexes which represents a local of valtype `f64`. 727 /// It is illegal to store a non-f64 valtype in this list. 728 free_locals_f64: std.ArrayListUnmanaged(u32) = .{}, 729 /// A list of indexes which represents a local of valtype `v127`. 730 /// It is illegal to store a non-v128 valtype in this list. 731 free_locals_v128: std.ArrayListUnmanaged(u32) = .{}, 732 733 /// When in debug mode, this tracks if no `finishAir` was missed. 734 /// Forgetting to call `finishAir` will cause the result to not be 735 /// stored in our `values` map and therefore cause bugs. 736 air_bookkeeping: @TypeOf(bookkeeping_init) = bookkeeping_init, 737 738 const bookkeeping_init = if (builtin.mode == .Debug) @as(usize, 0) else {}; 739 740 const InnerError = error{ 741 OutOfMemory, 742 /// An error occurred when trying to lower AIR to MIR. 743 CodegenFail, 744 /// Compiler implementation could not handle a large integer. 745 Overflow, 746 }; 747 748 pub fn deinit(func: *CodeGen) void { 749 // in case of an error and we still have branches 750 for (func.branches.items) |*branch| { 751 branch.deinit(func.gpa); 752 } 753 func.branches.deinit(func.gpa); 754 func.blocks.deinit(func.gpa); 755 func.locals.deinit(func.gpa); 756 func.simd_immediates.deinit(func.gpa); 757 func.mir_instructions.deinit(func.gpa); 758 func.mir_extra.deinit(func.gpa); 759 func.free_locals_i32.deinit(func.gpa); 760 func.free_locals_i64.deinit(func.gpa); 761 func.free_locals_f32.deinit(func.gpa); 762 func.free_locals_f64.deinit(func.gpa); 763 func.free_locals_v128.deinit(func.gpa); 764 func.* = undefined; 765 } 766 767 /// Sets `err_msg` on `CodeGen` and returns `error.CodegenFail` which is caught in link/Wasm.zig 768 fn fail(func: *CodeGen, comptime fmt: []const u8, args: anytype) InnerError { 769 const mod = func.bin_file.base.comp.module.?; 770 const src = LazySrcLoc.nodeOffset(0); 771 const src_loc = src.toSrcLoc(func.decl, mod); 772 func.err_msg = try Module.ErrorMsg.create(func.gpa, src_loc, fmt, args); 773 return error.CodegenFail; 774 } 775 776 /// Resolves the `WValue` for the given instruction `inst` 777 /// When the given instruction has a `Value`, it returns a constant instead 778 fn resolveInst(func: *CodeGen, ref: Air.Inst.Ref) InnerError!WValue { 779 var branch_index = func.branches.items.len; 780 while (branch_index > 0) : (branch_index -= 1) { 781 const branch = func.branches.items[branch_index - 1]; 782 if (branch.values.get(ref)) |value| { 783 return value; 784 } 785 } 786 787 // when we did not find an existing instruction, it 788 // means we must generate it from a constant. 789 // We always store constants in the most outer branch as they must never 790 // be removed. The most outer branch is always at index 0. 791 const gop = try func.branches.items[0].values.getOrPut(func.gpa, ref); 792 assert(!gop.found_existing); 793 794 const mod = func.bin_file.base.comp.module.?; 795 const val = (try func.air.value(ref, mod)).?; 796 const ty = func.typeOf(ref); 797 if (!ty.hasRuntimeBitsIgnoreComptime(mod) and !ty.isInt(mod) and !ty.isError(mod)) { 798 gop.value_ptr.* = WValue{ .none = {} }; 799 return gop.value_ptr.*; 800 } 801 802 // When we need to pass the value by reference (such as a struct), we will 803 // leverage `generateSymbol` to lower the constant to bytes and emit it 804 // to the 'rodata' section. We then return the index into the section as `WValue`. 805 // 806 // In the other cases, we will simply lower the constant to a value that fits 807 // into a single local (such as a pointer, integer, bool, etc). 808 const result = if (isByRef(ty, mod)) blk: { 809 const sym_index = try func.bin_file.lowerUnnamedConst(.{ .ty = ty, .val = val }, func.decl_index); 810 break :blk WValue{ .memory = sym_index }; 811 } else try func.lowerConstant(val, ty); 812 813 gop.value_ptr.* = result; 814 return result; 815 } 816 817 fn finishAir(func: *CodeGen, inst: Air.Inst.Index, result: WValue, operands: []const Air.Inst.Ref) void { 818 assert(operands.len <= Liveness.bpi - 1); 819 var tomb_bits = func.liveness.getTombBits(inst); 820 for (operands) |operand| { 821 const dies = @as(u1, @truncate(tomb_bits)) != 0; 822 tomb_bits >>= 1; 823 if (!dies) continue; 824 processDeath(func, operand); 825 } 826 827 // results of `none` can never be referenced. 828 if (result != .none) { 829 assert(result != .stack); // it's illegal to store a stack value as we cannot track its position 830 const branch = func.currentBranch(); 831 branch.values.putAssumeCapacityNoClobber(inst.toRef(), result); 832 } 833 834 if (builtin.mode == .Debug) { 835 func.air_bookkeeping += 1; 836 } 837 } 838 839 const Branch = struct { 840 values: ValueTable = .{}, 841 842 fn deinit(branch: *Branch, gpa: Allocator) void { 843 branch.values.deinit(gpa); 844 branch.* = undefined; 845 } 846 }; 847 848 inline fn currentBranch(func: *CodeGen) *Branch { 849 return &func.branches.items[func.branches.items.len - 1]; 850 } 851 852 const BigTomb = struct { 853 gen: *CodeGen, 854 inst: Air.Inst.Index, 855 lbt: Liveness.BigTomb, 856 857 fn feed(bt: *BigTomb, op_ref: Air.Inst.Ref) void { 858 const dies = bt.lbt.feed(); 859 if (!dies) return; 860 // This will be a nop for interned constants. 861 processDeath(bt.gen, op_ref); 862 } 863 864 fn finishAir(bt: *BigTomb, result: WValue) void { 865 assert(result != .stack); 866 if (result != .none) { 867 bt.gen.currentBranch().values.putAssumeCapacityNoClobber(bt.inst.toRef(), result); 868 } 869 870 if (builtin.mode == .Debug) { 871 bt.gen.air_bookkeeping += 1; 872 } 873 } 874 }; 875 876 fn iterateBigTomb(func: *CodeGen, inst: Air.Inst.Index, operand_count: usize) !BigTomb { 877 try func.currentBranch().values.ensureUnusedCapacity(func.gpa, operand_count + 1); 878 return BigTomb{ 879 .gen = func, 880 .inst = inst, 881 .lbt = func.liveness.iterateBigTomb(inst), 882 }; 883 } 884 885 fn processDeath(func: *CodeGen, ref: Air.Inst.Ref) void { 886 if (ref.toIndex() == null) return; 887 // Branches are currently only allowed to free locals allocated 888 // within their own branch. 889 // TODO: Upon branch consolidation free any locals if needed. 890 const value = func.currentBranch().values.getPtr(ref) orelse return; 891 if (value.* != .local) return; 892 const reserved_indexes = func.args.len + @intFromBool(func.return_value != .none); 893 if (value.local.value < reserved_indexes) { 894 return; // function arguments can never be re-used 895 } 896 log.debug("Decreasing reference for ref: %{d}, using local '{d}'", .{ @intFromEnum(ref.toIndex().?), value.local.value }); 897 value.local.references -= 1; // if this panics, a call to `reuseOperand` was forgotten by the developer 898 if (value.local.references == 0) { 899 value.free(func); 900 } 901 } 902 903 /// Appends a MIR instruction and returns its index within the list of instructions 904 fn addInst(func: *CodeGen, inst: Mir.Inst) error{OutOfMemory}!void { 905 try func.mir_instructions.append(func.gpa, inst); 906 } 907 908 fn addTag(func: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void { 909 try func.addInst(.{ .tag = tag, .data = .{ .tag = {} } }); 910 } 911 912 fn addExtended(func: *CodeGen, opcode: wasm.MiscOpcode) error{OutOfMemory}!void { 913 const extra_index = @as(u32, @intCast(func.mir_extra.items.len)); 914 try func.mir_extra.append(func.gpa, @intFromEnum(opcode)); 915 try func.addInst(.{ .tag = .misc_prefix, .data = .{ .payload = extra_index } }); 916 } 917 918 fn addLabel(func: *CodeGen, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void { 919 try func.addInst(.{ .tag = tag, .data = .{ .label = label } }); 920 } 921 922 fn addImm32(func: *CodeGen, imm: i32) error{OutOfMemory}!void { 923 try func.addInst(.{ .tag = .i32_const, .data = .{ .imm32 = imm } }); 924 } 925 926 /// Accepts an unsigned 64bit integer rather than a signed integer to 927 /// prevent us from having to bitcast multiple times as most values 928 /// within codegen are represented as unsigned rather than signed. 929 fn addImm64(func: *CodeGen, imm: u64) error{OutOfMemory}!void { 930 const extra_index = try func.addExtra(Mir.Imm64.fromU64(imm)); 931 try func.addInst(.{ .tag = .i64_const, .data = .{ .payload = extra_index } }); 932 } 933 934 /// Accepts the index into the list of 128bit-immediates 935 fn addImm128(func: *CodeGen, index: u32) error{OutOfMemory}!void { 936 const simd_values = func.simd_immediates.items[index]; 937 const extra_index = @as(u32, @intCast(func.mir_extra.items.len)); 938 // tag + 128bit value 939 try func.mir_extra.ensureUnusedCapacity(func.gpa, 5); 940 func.mir_extra.appendAssumeCapacity(std.wasm.simdOpcode(.v128_const)); 941 func.mir_extra.appendSliceAssumeCapacity(@alignCast(mem.bytesAsSlice(u32, &simd_values))); 942 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } }); 943 } 944 945 fn addFloat64(func: *CodeGen, float: f64) error{OutOfMemory}!void { 946 const extra_index = try func.addExtra(Mir.Float64.fromFloat64(float)); 947 try func.addInst(.{ .tag = .f64_const, .data = .{ .payload = extra_index } }); 948 } 949 950 /// Inserts an instruction to load/store from/to wasm's linear memory dependent on the given `tag`. 951 fn addMemArg(func: *CodeGen, tag: Mir.Inst.Tag, mem_arg: Mir.MemArg) error{OutOfMemory}!void { 952 const extra_index = try func.addExtra(mem_arg); 953 try func.addInst(.{ .tag = tag, .data = .{ .payload = extra_index } }); 954 } 955 956 /// Inserts an instruction from the 'atomics' feature which accesses wasm's linear memory dependent on the 957 /// given `tag`. 958 fn addAtomicMemArg(func: *CodeGen, tag: wasm.AtomicsOpcode, mem_arg: Mir.MemArg) error{OutOfMemory}!void { 959 const extra_index = try func.addExtra(@as(struct { val: u32 }, .{ .val = wasm.atomicsOpcode(tag) })); 960 _ = try func.addExtra(mem_arg); 961 try func.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } }); 962 } 963 964 /// Helper function to emit atomic mir opcodes. 965 fn addAtomicTag(func: *CodeGen, tag: wasm.AtomicsOpcode) error{OutOfMemory}!void { 966 const extra_index = try func.addExtra(@as(struct { val: u32 }, .{ .val = wasm.atomicsOpcode(tag) })); 967 try func.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } }); 968 } 969 970 /// Appends entries to `mir_extra` based on the type of `extra`. 971 /// Returns the index into `mir_extra` 972 fn addExtra(func: *CodeGen, extra: anytype) error{OutOfMemory}!u32 { 973 const fields = std.meta.fields(@TypeOf(extra)); 974 try func.mir_extra.ensureUnusedCapacity(func.gpa, fields.len); 975 return func.addExtraAssumeCapacity(extra); 976 } 977 978 /// Appends entries to `mir_extra` based on the type of `extra`. 979 /// Returns the index into `mir_extra` 980 fn addExtraAssumeCapacity(func: *CodeGen, extra: anytype) error{OutOfMemory}!u32 { 981 const fields = std.meta.fields(@TypeOf(extra)); 982 const result = @as(u32, @intCast(func.mir_extra.items.len)); 983 inline for (fields) |field| { 984 func.mir_extra.appendAssumeCapacity(switch (field.type) { 985 u32 => @field(extra, field.name), 986 else => |field_type| @compileError("Unsupported field type " ++ @typeName(field_type)), 987 }); 988 } 989 return result; 990 } 991 992 /// Using a given `Type`, returns the corresponding type 993 fn typeToValtype(ty: Type, mod: *Module) wasm.Valtype { 994 const target = mod.getTarget(); 995 const ip = &mod.intern_pool; 996 return switch (ty.zigTypeTag(mod)) { 997 .Float => switch (ty.floatBits(target)) { 998 16 => wasm.Valtype.i32, // stored/loaded as u16 999 32 => wasm.Valtype.f32, 1000 64 => wasm.Valtype.f64, 1001 80, 128 => wasm.Valtype.i64, 1002 else => unreachable, 1003 }, 1004 .Int, .Enum => blk: { 1005 const info = ty.intInfo(mod); 1006 if (info.bits <= 32) break :blk wasm.Valtype.i32; 1007 if (info.bits > 32 and info.bits <= 128) break :blk wasm.Valtype.i64; 1008 break :blk wasm.Valtype.i32; // represented as pointer to stack 1009 }, 1010 .Struct => { 1011 if (mod.typeToPackedStruct(ty)) |packed_struct| { 1012 return typeToValtype(Type.fromInterned(packed_struct.backingIntType(ip).*), mod); 1013 } else { 1014 return wasm.Valtype.i32; 1015 } 1016 }, 1017 .Vector => switch (determineSimdStoreStrategy(ty, mod)) { 1018 .direct => wasm.Valtype.v128, 1019 .unrolled => wasm.Valtype.i32, 1020 }, 1021 .Union => switch (ty.containerLayout(mod)) { 1022 .Packed => { 1023 const int_ty = mod.intType(.unsigned, @as(u16, @intCast(ty.bitSize(mod)))) catch @panic("out of memory"); 1024 return typeToValtype(int_ty, mod); 1025 }, 1026 else => wasm.Valtype.i32, 1027 }, 1028 else => wasm.Valtype.i32, // all represented as reference/immediate 1029 }; 1030 } 1031 1032 /// Using a given `Type`, returns the byte representation of its wasm value type 1033 fn genValtype(ty: Type, mod: *Module) u8 { 1034 return wasm.valtype(typeToValtype(ty, mod)); 1035 } 1036 1037 /// Using a given `Type`, returns the corresponding wasm value type 1038 /// Differently from `genValtype` this also allows `void` to create a block 1039 /// with no return type 1040 fn genBlockType(ty: Type, mod: *Module) u8 { 1041 return switch (ty.ip_index) { 1042 .void_type, .noreturn_type => wasm.block_empty, 1043 else => genValtype(ty, mod), 1044 }; 1045 } 1046 1047 /// Writes the bytecode depending on the given `WValue` in `val` 1048 fn emitWValue(func: *CodeGen, value: WValue) InnerError!void { 1049 switch (value) { 1050 .dead => unreachable, // reference to free'd `WValue` (missing reuseOperand?) 1051 .none, .stack => {}, // no-op 1052 .local => |idx| try func.addLabel(.local_get, idx.value), 1053 .imm32 => |val| try func.addImm32(@as(i32, @bitCast(val))), 1054 .imm64 => |val| try func.addImm64(val), 1055 .imm128 => |val| try func.addImm128(val), 1056 .float32 => |val| try func.addInst(.{ .tag = .f32_const, .data = .{ .float32 = val } }), 1057 .float64 => |val| try func.addFloat64(val), 1058 .memory => |ptr| { 1059 const extra_index = try func.addExtra(Mir.Memory{ .pointer = ptr, .offset = 0 }); 1060 try func.addInst(.{ .tag = .memory_address, .data = .{ .payload = extra_index } }); 1061 }, 1062 .memory_offset => |mem_off| { 1063 const extra_index = try func.addExtra(Mir.Memory{ .pointer = mem_off.pointer, .offset = mem_off.offset }); 1064 try func.addInst(.{ .tag = .memory_address, .data = .{ .payload = extra_index } }); 1065 }, 1066 .function_index => |index| try func.addLabel(.function_index, index), // write function index and generate relocation 1067 .stack_offset => try func.addLabel(.local_get, func.bottom_stack_value.local.value), // caller must ensure to address the offset 1068 } 1069 } 1070 1071 /// If given a local or stack-offset, increases the reference count by 1. 1072 /// The old `WValue` found at instruction `ref` is then replaced by the 1073 /// modified `WValue` and returned. When given a non-local or non-stack-offset, 1074 /// returns the given `operand` itfunc instead. 1075 fn reuseOperand(func: *CodeGen, ref: Air.Inst.Ref, operand: WValue) WValue { 1076 if (operand != .local and operand != .stack_offset) return operand; 1077 var new_value = operand; 1078 switch (new_value) { 1079 .local => |*local| local.references += 1, 1080 .stack_offset => |*stack_offset| stack_offset.references += 1, 1081 else => unreachable, 1082 } 1083 const old_value = func.getResolvedInst(ref); 1084 old_value.* = new_value; 1085 return new_value; 1086 } 1087 1088 /// From a reference, returns its resolved `WValue`. 1089 /// It's illegal to provide a `Air.Inst.Ref` that hasn't been resolved yet. 1090 fn getResolvedInst(func: *CodeGen, ref: Air.Inst.Ref) *WValue { 1091 var index = func.branches.items.len; 1092 while (index > 0) : (index -= 1) { 1093 const branch = func.branches.items[index - 1]; 1094 if (branch.values.getPtr(ref)) |value| { 1095 return value; 1096 } 1097 } 1098 unreachable; // developer-error: This can only be called on resolved instructions. Use `resolveInst` instead. 1099 } 1100 1101 /// Creates one locals for a given `Type`. 1102 /// Returns a corresponding `Wvalue` with `local` as active tag 1103 fn allocLocal(func: *CodeGen, ty: Type) InnerError!WValue { 1104 const mod = func.bin_file.base.comp.module.?; 1105 const valtype = typeToValtype(ty, mod); 1106 switch (valtype) { 1107 .i32 => if (func.free_locals_i32.popOrNull()) |index| { 1108 log.debug("reusing local ({d}) of type {}", .{ index, valtype }); 1109 return WValue{ .local = .{ .value = index, .references = 1 } }; 1110 }, 1111 .i64 => if (func.free_locals_i64.popOrNull()) |index| { 1112 log.debug("reusing local ({d}) of type {}", .{ index, valtype }); 1113 return WValue{ .local = .{ .value = index, .references = 1 } }; 1114 }, 1115 .f32 => if (func.free_locals_f32.popOrNull()) |index| { 1116 log.debug("reusing local ({d}) of type {}", .{ index, valtype }); 1117 return WValue{ .local = .{ .value = index, .references = 1 } }; 1118 }, 1119 .f64 => if (func.free_locals_f64.popOrNull()) |index| { 1120 log.debug("reusing local ({d}) of type {}", .{ index, valtype }); 1121 return WValue{ .local = .{ .value = index, .references = 1 } }; 1122 }, 1123 .v128 => if (func.free_locals_v128.popOrNull()) |index| { 1124 log.debug("reusing local ({d}) of type {}", .{ index, valtype }); 1125 return WValue{ .local = .{ .value = index, .references = 1 } }; 1126 }, 1127 } 1128 log.debug("new local of type {}", .{valtype}); 1129 // no local was free to be re-used, so allocate a new local instead 1130 return func.ensureAllocLocal(ty); 1131 } 1132 1133 /// Ensures a new local will be created. This is useful when it's useful 1134 /// to use a zero-initialized local. 1135 fn ensureAllocLocal(func: *CodeGen, ty: Type) InnerError!WValue { 1136 const mod = func.bin_file.base.comp.module.?; 1137 try func.locals.append(func.gpa, genValtype(ty, mod)); 1138 const initial_index = func.local_index; 1139 func.local_index += 1; 1140 return WValue{ .local = .{ .value = initial_index, .references = 1 } }; 1141 } 1142 1143 /// Generates a `wasm.Type` from a given function type. 1144 /// Memory is owned by the caller. 1145 fn genFunctype( 1146 gpa: Allocator, 1147 cc: std.builtin.CallingConvention, 1148 params: []const InternPool.Index, 1149 return_type: Type, 1150 mod: *Module, 1151 ) !wasm.Type { 1152 var temp_params = std.ArrayList(wasm.Valtype).init(gpa); 1153 defer temp_params.deinit(); 1154 var returns = std.ArrayList(wasm.Valtype).init(gpa); 1155 defer returns.deinit(); 1156 1157 if (firstParamSRet(cc, return_type, mod)) { 1158 try temp_params.append(.i32); // memory address is always a 32-bit handle 1159 } else if (return_type.hasRuntimeBitsIgnoreComptime(mod)) { 1160 if (cc == .C) { 1161 const res_classes = abi.classifyType(return_type, mod); 1162 assert(res_classes[0] == .direct and res_classes[1] == .none); 1163 const scalar_type = abi.scalarType(return_type, mod); 1164 try returns.append(typeToValtype(scalar_type, mod)); 1165 } else { 1166 try returns.append(typeToValtype(return_type, mod)); 1167 } 1168 } else if (return_type.isError(mod)) { 1169 try returns.append(.i32); 1170 } 1171 1172 // param types 1173 for (params) |param_type_ip| { 1174 const param_type = Type.fromInterned(param_type_ip); 1175 if (!param_type.hasRuntimeBitsIgnoreComptime(mod)) continue; 1176 1177 switch (cc) { 1178 .C => { 1179 const param_classes = abi.classifyType(param_type, mod); 1180 for (param_classes) |class| { 1181 if (class == .none) continue; 1182 if (class == .direct) { 1183 const scalar_type = abi.scalarType(param_type, mod); 1184 try temp_params.append(typeToValtype(scalar_type, mod)); 1185 } else { 1186 try temp_params.append(typeToValtype(param_type, mod)); 1187 } 1188 } 1189 }, 1190 else => if (isByRef(param_type, mod)) 1191 try temp_params.append(.i32) 1192 else 1193 try temp_params.append(typeToValtype(param_type, mod)), 1194 } 1195 } 1196 1197 return wasm.Type{ 1198 .params = try temp_params.toOwnedSlice(), 1199 .returns = try returns.toOwnedSlice(), 1200 }; 1201 } 1202 1203 pub fn generate( 1204 bin_file: *link.File, 1205 src_loc: Module.SrcLoc, 1206 func_index: InternPool.Index, 1207 air: Air, 1208 liveness: Liveness, 1209 code: *std.ArrayList(u8), 1210 debug_output: codegen.DebugInfoOutput, 1211 ) codegen.CodeGenError!codegen.Result { 1212 _ = src_loc; 1213 const mod = bin_file.options.module.?; 1214 const func = mod.funcInfo(func_index); 1215 var code_gen: CodeGen = .{ 1216 .gpa = bin_file.allocator, 1217 .air = air, 1218 .liveness = liveness, 1219 .code = code, 1220 .decl_index = func.owner_decl, 1221 .decl = mod.declPtr(func.owner_decl), 1222 .err_msg = undefined, 1223 .locals = .{}, 1224 .target = bin_file.options.target, 1225 .bin_file = bin_file.cast(link.File.Wasm).?, 1226 .debug_output = debug_output, 1227 .func_index = func_index, 1228 }; 1229 defer code_gen.deinit(); 1230 1231 genFunc(&code_gen) catch |err| switch (err) { 1232 error.CodegenFail => return codegen.Result{ .fail = code_gen.err_msg }, 1233 else => |e| return e, 1234 }; 1235 1236 return codegen.Result.ok; 1237 } 1238 1239 fn genFunc(func: *CodeGen) InnerError!void { 1240 const mod = func.bin_file.base.comp.module.?; 1241 const ip = &mod.intern_pool; 1242 const fn_info = mod.typeToFunc(func.decl.ty).?; 1243 var func_type = try genFunctype(func.gpa, fn_info.cc, fn_info.param_types.get(ip), Type.fromInterned(fn_info.return_type), mod); 1244 defer func_type.deinit(func.gpa); 1245 _ = try func.bin_file.storeDeclType(func.decl_index, func_type); 1246 1247 var cc_result = try func.resolveCallingConventionValues(func.decl.ty); 1248 defer cc_result.deinit(func.gpa); 1249 1250 func.args = cc_result.args; 1251 func.return_value = cc_result.return_value; 1252 1253 try func.addTag(.dbg_prologue_end); 1254 1255 try func.branches.append(func.gpa, .{}); 1256 // clean up outer branch 1257 defer { 1258 var outer_branch = func.branches.pop(); 1259 outer_branch.deinit(func.gpa); 1260 assert(func.branches.items.len == 0); // missing branch merge 1261 } 1262 // Generate MIR for function body 1263 try func.genBody(func.air.getMainBody()); 1264 1265 // In case we have a return value, but the last instruction is a noreturn (such as a while loop) 1266 // we emit an unreachable instruction to tell the stack validator that part will never be reached. 1267 if (func_type.returns.len != 0 and func.air.instructions.len > 0) { 1268 const inst: Air.Inst.Index = @enumFromInt(func.air.instructions.len - 1); 1269 const last_inst_ty = func.typeOfIndex(inst); 1270 if (!last_inst_ty.hasRuntimeBitsIgnoreComptime(mod) or last_inst_ty.isNoReturn(mod)) { 1271 try func.addTag(.@"unreachable"); 1272 } 1273 } 1274 // End of function body 1275 try func.addTag(.end); 1276 1277 try func.addTag(.dbg_epilogue_begin); 1278 1279 // check if we have to initialize and allocate anything into the stack frame. 1280 // If so, create enough stack space and insert the instructions at the front of the list. 1281 if (func.initial_stack_value != .none) { 1282 var prologue = std.ArrayList(Mir.Inst).init(func.gpa); 1283 defer prologue.deinit(); 1284 1285 // load stack pointer 1286 try prologue.append(.{ .tag = .global_get, .data = .{ .label = 0 } }); 1287 // store stack pointer so we can restore it when we return from the function 1288 try prologue.append(.{ .tag = .local_tee, .data = .{ .label = func.initial_stack_value.local.value } }); 1289 // get the total stack size 1290 const aligned_stack = func.stack_alignment.forward(func.stack_size); 1291 try prologue.append(.{ .tag = .i32_const, .data = .{ .imm32 = @intCast(aligned_stack) } }); 1292 // subtract it from the current stack pointer 1293 try prologue.append(.{ .tag = .i32_sub, .data = .{ .tag = {} } }); 1294 // Get negative stack aligment 1295 try prologue.append(.{ .tag = .i32_const, .data = .{ .imm32 = @as(i32, @intCast(func.stack_alignment.toByteUnitsOptional().?)) * -1 } }); 1296 // Bitwise-and the value to get the new stack pointer to ensure the pointers are aligned with the abi alignment 1297 try prologue.append(.{ .tag = .i32_and, .data = .{ .tag = {} } }); 1298 // store the current stack pointer as the bottom, which will be used to calculate all stack pointer offsets 1299 try prologue.append(.{ .tag = .local_tee, .data = .{ .label = func.bottom_stack_value.local.value } }); 1300 // Store the current stack pointer value into the global stack pointer so other function calls will 1301 // start from this value instead and not overwrite the current stack. 1302 try prologue.append(.{ .tag = .global_set, .data = .{ .label = 0 } }); 1303 1304 // reserve space and insert all prologue instructions at the front of the instruction list 1305 // We insert them in reserve order as there is no insertSlice in multiArrayList. 1306 try func.mir_instructions.ensureUnusedCapacity(func.gpa, prologue.items.len); 1307 for (prologue.items, 0..) |_, index| { 1308 const inst = prologue.items[prologue.items.len - 1 - index]; 1309 func.mir_instructions.insertAssumeCapacity(0, inst); 1310 } 1311 } 1312 1313 var mir: Mir = .{ 1314 .instructions = func.mir_instructions.toOwnedSlice(), 1315 .extra = try func.mir_extra.toOwnedSlice(func.gpa), 1316 }; 1317 defer mir.deinit(func.gpa); 1318 1319 var emit: Emit = .{ 1320 .mir = mir, 1321 .bin_file = func.bin_file, 1322 .code = func.code, 1323 .locals = func.locals.items, 1324 .decl_index = func.decl_index, 1325 .dbg_output = func.debug_output, 1326 .prev_di_line = 0, 1327 .prev_di_column = 0, 1328 .prev_di_offset = 0, 1329 }; 1330 1331 emit.emitMir() catch |err| switch (err) { 1332 error.EmitFail => { 1333 func.err_msg = emit.error_msg.?; 1334 return error.CodegenFail; 1335 }, 1336 else => |e| return e, 1337 }; 1338 } 1339 1340 const CallWValues = struct { 1341 args: []WValue, 1342 return_value: WValue, 1343 1344 fn deinit(values: *CallWValues, gpa: Allocator) void { 1345 gpa.free(values.args); 1346 values.* = undefined; 1347 } 1348 }; 1349 1350 fn resolveCallingConventionValues(func: *CodeGen, fn_ty: Type) InnerError!CallWValues { 1351 const mod = func.bin_file.base.comp.module.?; 1352 const ip = &mod.intern_pool; 1353 const fn_info = mod.typeToFunc(fn_ty).?; 1354 const cc = fn_info.cc; 1355 var result: CallWValues = .{ 1356 .args = &.{}, 1357 .return_value = .none, 1358 }; 1359 if (cc == .Naked) return result; 1360 1361 var args = std.ArrayList(WValue).init(func.gpa); 1362 defer args.deinit(); 1363 1364 // Check if we store the result as a pointer to the stack rather than 1365 // by value 1366 if (firstParamSRet(fn_info.cc, Type.fromInterned(fn_info.return_type), mod)) { 1367 // the sret arg will be passed as first argument, therefore we 1368 // set the `return_value` before allocating locals for regular args. 1369 result.return_value = .{ .local = .{ .value = func.local_index, .references = 1 } }; 1370 func.local_index += 1; 1371 } 1372 1373 switch (cc) { 1374 .Unspecified => { 1375 for (fn_info.param_types.get(ip)) |ty| { 1376 if (!Type.fromInterned(ty).hasRuntimeBitsIgnoreComptime(mod)) { 1377 continue; 1378 } 1379 1380 try args.append(.{ .local = .{ .value = func.local_index, .references = 1 } }); 1381 func.local_index += 1; 1382 } 1383 }, 1384 .C => { 1385 for (fn_info.param_types.get(ip)) |ty| { 1386 const ty_classes = abi.classifyType(Type.fromInterned(ty), mod); 1387 for (ty_classes) |class| { 1388 if (class == .none) continue; 1389 try args.append(.{ .local = .{ .value = func.local_index, .references = 1 } }); 1390 func.local_index += 1; 1391 } 1392 } 1393 }, 1394 else => return func.fail("calling convention '{s}' not supported for Wasm", .{@tagName(cc)}), 1395 } 1396 result.args = try args.toOwnedSlice(); 1397 return result; 1398 } 1399 1400 fn firstParamSRet(cc: std.builtin.CallingConvention, return_type: Type, mod: *Module) bool { 1401 switch (cc) { 1402 .Unspecified, .Inline => return isByRef(return_type, mod), 1403 .C => { 1404 const ty_classes = abi.classifyType(return_type, mod); 1405 if (ty_classes[0] == .indirect) return true; 1406 if (ty_classes[0] == .direct and ty_classes[1] == .direct) return true; 1407 return false; 1408 }, 1409 else => return false, 1410 } 1411 } 1412 1413 /// Lowers a Zig type and its value based on a given calling convention to ensure 1414 /// it matches the ABI. 1415 fn lowerArg(func: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WValue) !void { 1416 if (cc != .C) { 1417 return func.lowerToStack(value); 1418 } 1419 1420 const mod = func.bin_file.base.comp.module.?; 1421 const ty_classes = abi.classifyType(ty, mod); 1422 assert(ty_classes[0] != .none); 1423 switch (ty.zigTypeTag(mod)) { 1424 .Struct, .Union => { 1425 if (ty_classes[0] == .indirect) { 1426 return func.lowerToStack(value); 1427 } 1428 assert(ty_classes[0] == .direct); 1429 const scalar_type = abi.scalarType(ty, mod); 1430 const abi_size = scalar_type.abiSize(mod); 1431 try func.emitWValue(value); 1432 1433 // When the value lives in the virtual stack, we must load it onto the actual stack 1434 if (value != .imm32 and value != .imm64) { 1435 const opcode = buildOpcode(.{ 1436 .op = .load, 1437 .width = @as(u8, @intCast(abi_size)), 1438 .signedness = if (scalar_type.isSignedInt(mod)) .signed else .unsigned, 1439 .valtype1 = typeToValtype(scalar_type, mod), 1440 }); 1441 try func.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{ 1442 .offset = value.offset(), 1443 .alignment = @intCast(scalar_type.abiAlignment(mod).toByteUnitsOptional().?), 1444 }); 1445 } 1446 }, 1447 .Int, .Float => { 1448 if (ty_classes[1] == .none) { 1449 return func.lowerToStack(value); 1450 } 1451 assert(ty_classes[0] == .direct and ty_classes[1] == .direct); 1452 assert(ty.abiSize(mod) == 16); 1453 // in this case we have an integer or float that must be lowered as 2 i64's. 1454 try func.emitWValue(value); 1455 try func.addMemArg(.i64_load, .{ .offset = value.offset(), .alignment = 8 }); 1456 try func.emitWValue(value); 1457 try func.addMemArg(.i64_load, .{ .offset = value.offset() + 8, .alignment = 8 }); 1458 }, 1459 else => return func.lowerToStack(value), 1460 } 1461 } 1462 1463 /// Lowers a `WValue` to the stack. This means when the `value` results in 1464 /// `.stack_offset` we calculate the pointer of this offset and use that. 1465 /// The value is left on the stack, and not stored in any temporary. 1466 fn lowerToStack(func: *CodeGen, value: WValue) !void { 1467 switch (value) { 1468 .stack_offset => |offset| { 1469 try func.emitWValue(value); 1470 if (offset.value > 0) { 1471 switch (func.arch()) { 1472 .wasm32 => { 1473 try func.addImm32(@as(i32, @bitCast(offset.value))); 1474 try func.addTag(.i32_add); 1475 }, 1476 .wasm64 => { 1477 try func.addImm64(offset.value); 1478 try func.addTag(.i64_add); 1479 }, 1480 else => unreachable, 1481 } 1482 } 1483 }, 1484 else => try func.emitWValue(value), 1485 } 1486 } 1487 1488 /// Creates a local for the initial stack value 1489 /// Asserts `initial_stack_value` is `.none` 1490 fn initializeStack(func: *CodeGen) !void { 1491 assert(func.initial_stack_value == .none); 1492 // Reserve a local to store the current stack pointer 1493 // We can later use this local to set the stack pointer back to the value 1494 // we have stored here. 1495 func.initial_stack_value = try func.ensureAllocLocal(Type.usize); 1496 // Also reserve a local to store the bottom stack value 1497 func.bottom_stack_value = try func.ensureAllocLocal(Type.usize); 1498 } 1499 1500 /// Reads the stack pointer from `Context.initial_stack_value` and writes it 1501 /// to the global stack pointer variable 1502 fn restoreStackPointer(func: *CodeGen) !void { 1503 // only restore the pointer if it was initialized 1504 if (func.initial_stack_value == .none) return; 1505 // Get the original stack pointer's value 1506 try func.emitWValue(func.initial_stack_value); 1507 1508 // save its value in the global stack pointer 1509 try func.addLabel(.global_set, 0); 1510 } 1511 1512 /// From a given type, will create space on the virtual stack to store the value of such type. 1513 /// This returns a `WValue` with its active tag set to `local`, containing the index to the local 1514 /// that points to the position on the virtual stack. This function should be used instead of 1515 /// moveStack unless a local was already created to store the pointer. 1516 /// 1517 /// Asserts Type has codegenbits 1518 fn allocStack(func: *CodeGen, ty: Type) !WValue { 1519 const mod = func.bin_file.base.comp.module.?; 1520 assert(ty.hasRuntimeBitsIgnoreComptime(mod)); 1521 if (func.initial_stack_value == .none) { 1522 try func.initializeStack(); 1523 } 1524 1525 const abi_size = std.math.cast(u32, ty.abiSize(mod)) orelse { 1526 return func.fail("Type {} with ABI size of {d} exceeds stack frame size", .{ 1527 ty.fmt(mod), ty.abiSize(mod), 1528 }); 1529 }; 1530 const abi_align = ty.abiAlignment(mod); 1531 1532 func.stack_alignment = func.stack_alignment.max(abi_align); 1533 1534 const offset: u32 = @intCast(abi_align.forward(func.stack_size)); 1535 defer func.stack_size = offset + abi_size; 1536 1537 return WValue{ .stack_offset = .{ .value = offset, .references = 1 } }; 1538 } 1539 1540 /// From a given AIR instruction generates a pointer to the stack where 1541 /// the value of its type will live. 1542 /// This is different from allocStack where this will use the pointer's alignment 1543 /// if it is set, to ensure the stack alignment will be set correctly. 1544 fn allocStackPtr(func: *CodeGen, inst: Air.Inst.Index) !WValue { 1545 const mod = func.bin_file.base.comp.module.?; 1546 const ptr_ty = func.typeOfIndex(inst); 1547 const pointee_ty = ptr_ty.childType(mod); 1548 1549 if (func.initial_stack_value == .none) { 1550 try func.initializeStack(); 1551 } 1552 1553 if (!pointee_ty.hasRuntimeBitsIgnoreComptime(mod)) { 1554 return func.allocStack(Type.usize); // create a value containing just the stack pointer. 1555 } 1556 1557 const abi_alignment = ptr_ty.ptrAlignment(mod); 1558 const abi_size = std.math.cast(u32, pointee_ty.abiSize(mod)) orelse { 1559 return func.fail("Type {} with ABI size of {d} exceeds stack frame size", .{ 1560 pointee_ty.fmt(mod), pointee_ty.abiSize(mod), 1561 }); 1562 }; 1563 func.stack_alignment = func.stack_alignment.max(abi_alignment); 1564 1565 const offset: u32 = @intCast(abi_alignment.forward(func.stack_size)); 1566 defer func.stack_size = offset + abi_size; 1567 1568 return WValue{ .stack_offset = .{ .value = offset, .references = 1 } }; 1569 } 1570 1571 /// From given zig bitsize, returns the wasm bitsize 1572 fn toWasmBits(bits: u16) ?u16 { 1573 return for ([_]u16{ 32, 64, 128 }) |wasm_bits| { 1574 if (bits <= wasm_bits) return wasm_bits; 1575 } else null; 1576 } 1577 1578 /// Performs a copy of bytes for a given type. Copying all bytes 1579 /// from rhs to lhs. 1580 fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { 1581 // When bulk_memory is enabled, we lower it to wasm's memcpy instruction. 1582 // If not, we lower it ourselves manually 1583 if (std.Target.wasm.featureSetHas(func.target.cpu.features, .bulk_memory)) { 1584 try func.lowerToStack(dst); 1585 try func.lowerToStack(src); 1586 try func.emitWValue(len); 1587 try func.addExtended(.memory_copy); 1588 return; 1589 } 1590 1591 // when the length is comptime-known, rather than a runtime value, we can optimize the generated code by having 1592 // the loop during codegen, rather than inserting a runtime loop into the binary. 1593 switch (len) { 1594 .imm32, .imm64 => blk: { 1595 const length = switch (len) { 1596 .imm32 => |val| val, 1597 .imm64 => |val| val, 1598 else => unreachable, 1599 }; 1600 // if the size (length) is more than 32 bytes, we use a runtime loop instead to prevent 1601 // binary size bloat. 1602 if (length > 32) break :blk; 1603 var offset: u32 = 0; 1604 const lhs_base = dst.offset(); 1605 const rhs_base = src.offset(); 1606 while (offset < length) : (offset += 1) { 1607 // get dst's address to store the result 1608 try func.emitWValue(dst); 1609 // load byte from src's address 1610 try func.emitWValue(src); 1611 switch (func.arch()) { 1612 .wasm32 => { 1613 try func.addMemArg(.i32_load8_u, .{ .offset = rhs_base + offset, .alignment = 1 }); 1614 try func.addMemArg(.i32_store8, .{ .offset = lhs_base + offset, .alignment = 1 }); 1615 }, 1616 .wasm64 => { 1617 try func.addMemArg(.i64_load8_u, .{ .offset = rhs_base + offset, .alignment = 1 }); 1618 try func.addMemArg(.i64_store8, .{ .offset = lhs_base + offset, .alignment = 1 }); 1619 }, 1620 else => unreachable, 1621 } 1622 } 1623 return; 1624 }, 1625 else => {}, 1626 } 1627 1628 // allocate a local for the offset, and set it to 0. 1629 // This to ensure that inside loops we correctly re-set the counter. 1630 var offset = try func.allocLocal(Type.usize); // local for counter 1631 defer offset.free(func); 1632 switch (func.arch()) { 1633 .wasm32 => try func.addImm32(0), 1634 .wasm64 => try func.addImm64(0), 1635 else => unreachable, 1636 } 1637 try func.addLabel(.local_set, offset.local.value); 1638 1639 // outer block to jump to when loop is done 1640 try func.startBlock(.block, wasm.block_empty); 1641 try func.startBlock(.loop, wasm.block_empty); 1642 1643 // loop condition (offset == length -> break) 1644 { 1645 try func.emitWValue(offset); 1646 try func.emitWValue(len); 1647 switch (func.arch()) { 1648 .wasm32 => try func.addTag(.i32_eq), 1649 .wasm64 => try func.addTag(.i64_eq), 1650 else => unreachable, 1651 } 1652 try func.addLabel(.br_if, 1); // jump out of loop into outer block (finished) 1653 } 1654 1655 // get dst ptr 1656 { 1657 try func.emitWValue(dst); 1658 try func.emitWValue(offset); 1659 switch (func.arch()) { 1660 .wasm32 => try func.addTag(.i32_add), 1661 .wasm64 => try func.addTag(.i64_add), 1662 else => unreachable, 1663 } 1664 } 1665 1666 // get src value and also store in dst 1667 { 1668 try func.emitWValue(src); 1669 try func.emitWValue(offset); 1670 switch (func.arch()) { 1671 .wasm32 => { 1672 try func.addTag(.i32_add); 1673 try func.addMemArg(.i32_load8_u, .{ .offset = src.offset(), .alignment = 1 }); 1674 try func.addMemArg(.i32_store8, .{ .offset = dst.offset(), .alignment = 1 }); 1675 }, 1676 .wasm64 => { 1677 try func.addTag(.i64_add); 1678 try func.addMemArg(.i64_load8_u, .{ .offset = src.offset(), .alignment = 1 }); 1679 try func.addMemArg(.i64_store8, .{ .offset = dst.offset(), .alignment = 1 }); 1680 }, 1681 else => unreachable, 1682 } 1683 } 1684 1685 // increment loop counter 1686 { 1687 try func.emitWValue(offset); 1688 switch (func.arch()) { 1689 .wasm32 => { 1690 try func.addImm32(1); 1691 try func.addTag(.i32_add); 1692 }, 1693 .wasm64 => { 1694 try func.addImm64(1); 1695 try func.addTag(.i64_add); 1696 }, 1697 else => unreachable, 1698 } 1699 try func.addLabel(.local_set, offset.local.value); 1700 try func.addLabel(.br, 0); // jump to start of loop 1701 } 1702 try func.endBlock(); // close off loop block 1703 try func.endBlock(); // close off outer block 1704 } 1705 1706 fn ptrSize(func: *const CodeGen) u16 { 1707 return @divExact(func.target.ptrBitWidth(), 8); 1708 } 1709 1710 fn arch(func: *const CodeGen) std.Target.Cpu.Arch { 1711 return func.target.cpu.arch; 1712 } 1713 1714 /// For a given `Type`, will return true when the type will be passed 1715 /// by reference, rather than by value 1716 fn isByRef(ty: Type, mod: *Module) bool { 1717 const ip = &mod.intern_pool; 1718 const target = mod.getTarget(); 1719 switch (ty.zigTypeTag(mod)) { 1720 .Type, 1721 .ComptimeInt, 1722 .ComptimeFloat, 1723 .EnumLiteral, 1724 .Undefined, 1725 .Null, 1726 .Opaque, 1727 => unreachable, 1728 1729 .NoReturn, 1730 .Void, 1731 .Bool, 1732 .ErrorSet, 1733 .Fn, 1734 .Enum, 1735 .AnyFrame, 1736 => return false, 1737 1738 .Array, 1739 .Frame, 1740 => return ty.hasRuntimeBitsIgnoreComptime(mod), 1741 .Union => { 1742 if (mod.typeToUnion(ty)) |union_obj| { 1743 if (union_obj.getLayout(ip) == .Packed) { 1744 return ty.abiSize(mod) > 8; 1745 } 1746 } 1747 return ty.hasRuntimeBitsIgnoreComptime(mod); 1748 }, 1749 .Struct => { 1750 if (mod.typeToPackedStruct(ty)) |packed_struct| { 1751 return isByRef(Type.fromInterned(packed_struct.backingIntType(ip).*), mod); 1752 } 1753 return ty.hasRuntimeBitsIgnoreComptime(mod); 1754 }, 1755 .Vector => return determineSimdStoreStrategy(ty, mod) == .unrolled, 1756 .Int => return ty.intInfo(mod).bits > 64, 1757 .Float => return ty.floatBits(target) > 64, 1758 .ErrorUnion => { 1759 const pl_ty = ty.errorUnionPayload(mod); 1760 if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) { 1761 return false; 1762 } 1763 return true; 1764 }, 1765 .Optional => { 1766 if (ty.isPtrLikeOptional(mod)) return false; 1767 const pl_type = ty.optionalChild(mod); 1768 if (pl_type.zigTypeTag(mod) == .ErrorSet) return false; 1769 return pl_type.hasRuntimeBitsIgnoreComptime(mod); 1770 }, 1771 .Pointer => { 1772 // Slices act like struct and will be passed by reference 1773 if (ty.isSlice(mod)) return true; 1774 return false; 1775 }, 1776 } 1777 } 1778 1779 const SimdStoreStrategy = enum { 1780 direct, 1781 unrolled, 1782 }; 1783 1784 /// For a given vector type, returns the `SimdStoreStrategy`. 1785 /// This means when a given type is 128 bits and either the simd128 or relaxed-simd 1786 /// features are enabled, the function will return `.direct`. This would allow to store 1787 /// it using a instruction, rather than an unrolled version. 1788 fn determineSimdStoreStrategy(ty: Type, mod: *Module) SimdStoreStrategy { 1789 std.debug.assert(ty.zigTypeTag(mod) == .Vector); 1790 if (ty.bitSize(mod) != 128) return .unrolled; 1791 const hasFeature = std.Target.wasm.featureSetHas; 1792 const target = mod.getTarget(); 1793 const features = target.cpu.features; 1794 if (hasFeature(features, .relaxed_simd) or hasFeature(features, .simd128)) { 1795 return .direct; 1796 } 1797 return .unrolled; 1798 } 1799 1800 /// Creates a new local for a pointer that points to memory with given offset. 1801 /// This can be used to get a pointer to a struct field, error payload, etc. 1802 /// By providing `modify` as action, it will modify the given `ptr_value` instead of making a new 1803 /// local value to store the pointer. This allows for local re-use and improves binary size. 1804 fn buildPointerOffset(func: *CodeGen, ptr_value: WValue, offset: u64, action: enum { modify, new }) InnerError!WValue { 1805 // do not perform arithmetic when offset is 0. 1806 if (offset == 0 and ptr_value.offset() == 0 and action == .modify) return ptr_value; 1807 const result_ptr: WValue = switch (action) { 1808 .new => try func.ensureAllocLocal(Type.usize), 1809 .modify => ptr_value, 1810 }; 1811 try func.emitWValue(ptr_value); 1812 if (offset + ptr_value.offset() > 0) { 1813 switch (func.arch()) { 1814 .wasm32 => { 1815 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(offset + ptr_value.offset()))))); 1816 try func.addTag(.i32_add); 1817 }, 1818 .wasm64 => { 1819 try func.addImm64(offset + ptr_value.offset()); 1820 try func.addTag(.i64_add); 1821 }, 1822 else => unreachable, 1823 } 1824 } 1825 try func.addLabel(.local_set, result_ptr.local.value); 1826 return result_ptr; 1827 } 1828 1829 fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 1830 const air_tags = func.air.instructions.items(.tag); 1831 return switch (air_tags[@intFromEnum(inst)]) { 1832 .inferred_alloc, .inferred_alloc_comptime => unreachable, 1833 1834 .add => func.airBinOp(inst, .add), 1835 .add_sat => func.airSatBinOp(inst, .add), 1836 .add_wrap => func.airWrapBinOp(inst, .add), 1837 .sub => func.airBinOp(inst, .sub), 1838 .sub_sat => func.airSatBinOp(inst, .sub), 1839 .sub_wrap => func.airWrapBinOp(inst, .sub), 1840 .mul => func.airBinOp(inst, .mul), 1841 .mul_wrap => func.airWrapBinOp(inst, .mul), 1842 .div_float, .div_exact => func.airDiv(inst), 1843 .div_trunc => func.airDivTrunc(inst), 1844 .div_floor => func.airDivFloor(inst), 1845 .bit_and => func.airBinOp(inst, .@"and"), 1846 .bit_or => func.airBinOp(inst, .@"or"), 1847 .bool_and => func.airBinOp(inst, .@"and"), 1848 .bool_or => func.airBinOp(inst, .@"or"), 1849 .rem => func.airBinOp(inst, .rem), 1850 .mod => func.airMod(inst), 1851 .shl => func.airWrapBinOp(inst, .shl), 1852 .shl_exact => func.airBinOp(inst, .shl), 1853 .shl_sat => func.airShlSat(inst), 1854 .shr, .shr_exact => func.airBinOp(inst, .shr), 1855 .xor => func.airBinOp(inst, .xor), 1856 .max => func.airMaxMin(inst, .max), 1857 .min => func.airMaxMin(inst, .min), 1858 .mul_add => func.airMulAdd(inst), 1859 1860 .sqrt => func.airUnaryFloatOp(inst, .sqrt), 1861 .sin => func.airUnaryFloatOp(inst, .sin), 1862 .cos => func.airUnaryFloatOp(inst, .cos), 1863 .tan => func.airUnaryFloatOp(inst, .tan), 1864 .exp => func.airUnaryFloatOp(inst, .exp), 1865 .exp2 => func.airUnaryFloatOp(inst, .exp2), 1866 .log => func.airUnaryFloatOp(inst, .log), 1867 .log2 => func.airUnaryFloatOp(inst, .log2), 1868 .log10 => func.airUnaryFloatOp(inst, .log10), 1869 .floor => func.airUnaryFloatOp(inst, .floor), 1870 .ceil => func.airUnaryFloatOp(inst, .ceil), 1871 .round => func.airUnaryFloatOp(inst, .round), 1872 .trunc_float => func.airUnaryFloatOp(inst, .trunc), 1873 .neg => func.airUnaryFloatOp(inst, .neg), 1874 1875 .abs => func.airAbs(inst), 1876 1877 .add_with_overflow => func.airAddSubWithOverflow(inst, .add), 1878 .sub_with_overflow => func.airAddSubWithOverflow(inst, .sub), 1879 .shl_with_overflow => func.airShlWithOverflow(inst), 1880 .mul_with_overflow => func.airMulWithOverflow(inst), 1881 1882 .clz => func.airClz(inst), 1883 .ctz => func.airCtz(inst), 1884 1885 .cmp_eq => func.airCmp(inst, .eq), 1886 .cmp_gte => func.airCmp(inst, .gte), 1887 .cmp_gt => func.airCmp(inst, .gt), 1888 .cmp_lte => func.airCmp(inst, .lte), 1889 .cmp_lt => func.airCmp(inst, .lt), 1890 .cmp_neq => func.airCmp(inst, .neq), 1891 1892 .cmp_vector => func.airCmpVector(inst), 1893 .cmp_lt_errors_len => func.airCmpLtErrorsLen(inst), 1894 1895 .array_elem_val => func.airArrayElemVal(inst), 1896 .array_to_slice => func.airArrayToSlice(inst), 1897 .alloc => func.airAlloc(inst), 1898 .arg => func.airArg(inst), 1899 .bitcast => func.airBitcast(inst), 1900 .block => func.airBlock(inst), 1901 .trap => func.airTrap(inst), 1902 .breakpoint => func.airBreakpoint(inst), 1903 .br => func.airBr(inst), 1904 .int_from_bool => func.airIntFromBool(inst), 1905 .cond_br => func.airCondBr(inst), 1906 .intcast => func.airIntcast(inst), 1907 .fptrunc => func.airFptrunc(inst), 1908 .fpext => func.airFpext(inst), 1909 .int_from_float => func.airIntFromFloat(inst), 1910 .float_from_int => func.airFloatFromInt(inst), 1911 .get_union_tag => func.airGetUnionTag(inst), 1912 1913 .@"try" => func.airTry(inst), 1914 .try_ptr => func.airTryPtr(inst), 1915 1916 // TODO 1917 .dbg_inline_begin, 1918 .dbg_inline_end, 1919 .dbg_block_begin, 1920 .dbg_block_end, 1921 => func.finishAir(inst, .none, &.{}), 1922 1923 .dbg_var_ptr => func.airDbgVar(inst, true), 1924 .dbg_var_val => func.airDbgVar(inst, false), 1925 1926 .dbg_stmt => func.airDbgStmt(inst), 1927 1928 .call => func.airCall(inst, .auto), 1929 .call_always_tail => func.airCall(inst, .always_tail), 1930 .call_never_tail => func.airCall(inst, .never_tail), 1931 .call_never_inline => func.airCall(inst, .never_inline), 1932 1933 .is_err => func.airIsErr(inst, .i32_ne), 1934 .is_non_err => func.airIsErr(inst, .i32_eq), 1935 1936 .is_null => func.airIsNull(inst, .i32_eq, .value), 1937 .is_non_null => func.airIsNull(inst, .i32_ne, .value), 1938 .is_null_ptr => func.airIsNull(inst, .i32_eq, .ptr), 1939 .is_non_null_ptr => func.airIsNull(inst, .i32_ne, .ptr), 1940 1941 .load => func.airLoad(inst), 1942 .loop => func.airLoop(inst), 1943 .memset => func.airMemset(inst, false), 1944 .memset_safe => func.airMemset(inst, true), 1945 .not => func.airNot(inst), 1946 .optional_payload => func.airOptionalPayload(inst), 1947 .optional_payload_ptr => func.airOptionalPayloadPtr(inst), 1948 .optional_payload_ptr_set => func.airOptionalPayloadPtrSet(inst), 1949 .ptr_add => func.airPtrBinOp(inst, .add), 1950 .ptr_sub => func.airPtrBinOp(inst, .sub), 1951 .ptr_elem_ptr => func.airPtrElemPtr(inst), 1952 .ptr_elem_val => func.airPtrElemVal(inst), 1953 .int_from_ptr => func.airIntFromPtr(inst), 1954 .ret => func.airRet(inst), 1955 .ret_ptr => func.airRetPtr(inst), 1956 .ret_load => func.airRetLoad(inst), 1957 .splat => func.airSplat(inst), 1958 .select => func.airSelect(inst), 1959 .shuffle => func.airShuffle(inst), 1960 .reduce => func.airReduce(inst), 1961 .aggregate_init => func.airAggregateInit(inst), 1962 .union_init => func.airUnionInit(inst), 1963 .prefetch => func.airPrefetch(inst), 1964 .popcount => func.airPopcount(inst), 1965 .byte_swap => func.airByteSwap(inst), 1966 1967 .slice => func.airSlice(inst), 1968 .slice_len => func.airSliceLen(inst), 1969 .slice_elem_val => func.airSliceElemVal(inst), 1970 .slice_elem_ptr => func.airSliceElemPtr(inst), 1971 .slice_ptr => func.airSlicePtr(inst), 1972 .ptr_slice_len_ptr => func.airPtrSliceFieldPtr(inst, func.ptrSize()), 1973 .ptr_slice_ptr_ptr => func.airPtrSliceFieldPtr(inst, 0), 1974 .store => func.airStore(inst, false), 1975 .store_safe => func.airStore(inst, true), 1976 1977 .set_union_tag => func.airSetUnionTag(inst), 1978 .struct_field_ptr => func.airStructFieldPtr(inst), 1979 .struct_field_ptr_index_0 => func.airStructFieldPtrIndex(inst, 0), 1980 .struct_field_ptr_index_1 => func.airStructFieldPtrIndex(inst, 1), 1981 .struct_field_ptr_index_2 => func.airStructFieldPtrIndex(inst, 2), 1982 .struct_field_ptr_index_3 => func.airStructFieldPtrIndex(inst, 3), 1983 .struct_field_val => func.airStructFieldVal(inst), 1984 .field_parent_ptr => func.airFieldParentPtr(inst), 1985 1986 .switch_br => func.airSwitchBr(inst), 1987 .trunc => func.airTrunc(inst), 1988 .unreach => func.airUnreachable(inst), 1989 1990 .wrap_optional => func.airWrapOptional(inst), 1991 .unwrap_errunion_payload => func.airUnwrapErrUnionPayload(inst, false), 1992 .unwrap_errunion_payload_ptr => func.airUnwrapErrUnionPayload(inst, true), 1993 .unwrap_errunion_err => func.airUnwrapErrUnionError(inst, false), 1994 .unwrap_errunion_err_ptr => func.airUnwrapErrUnionError(inst, true), 1995 .wrap_errunion_payload => func.airWrapErrUnionPayload(inst), 1996 .wrap_errunion_err => func.airWrapErrUnionErr(inst), 1997 .errunion_payload_ptr_set => func.airErrUnionPayloadPtrSet(inst), 1998 .error_name => func.airErrorName(inst), 1999 2000 .wasm_memory_size => func.airWasmMemorySize(inst), 2001 .wasm_memory_grow => func.airWasmMemoryGrow(inst), 2002 2003 .memcpy => func.airMemcpy(inst), 2004 2005 .ret_addr => func.airRetAddr(inst), 2006 .tag_name => func.airTagName(inst), 2007 2008 .error_set_has_value => func.airErrorSetHasValue(inst), 2009 .frame_addr => func.airFrameAddress(inst), 2010 2011 .mul_sat, 2012 .assembly, 2013 .bit_reverse, 2014 .is_err_ptr, 2015 .is_non_err_ptr, 2016 2017 .err_return_trace, 2018 .set_err_return_trace, 2019 .save_err_return_trace_index, 2020 .is_named_enum_value, 2021 .addrspace_cast, 2022 .vector_store_elem, 2023 .c_va_arg, 2024 .c_va_copy, 2025 .c_va_end, 2026 .c_va_start, 2027 => |tag| return func.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), 2028 2029 .atomic_load => func.airAtomicLoad(inst), 2030 .atomic_store_unordered, 2031 .atomic_store_monotonic, 2032 .atomic_store_release, 2033 .atomic_store_seq_cst, 2034 // in WebAssembly, all atomic instructions are sequentially ordered. 2035 => func.airAtomicStore(inst), 2036 .atomic_rmw => func.airAtomicRmw(inst), 2037 .cmpxchg_weak => func.airCmpxchg(inst), 2038 .cmpxchg_strong => func.airCmpxchg(inst), 2039 .fence => func.airFence(inst), 2040 2041 .add_optimized, 2042 .sub_optimized, 2043 .mul_optimized, 2044 .div_float_optimized, 2045 .div_trunc_optimized, 2046 .div_floor_optimized, 2047 .div_exact_optimized, 2048 .rem_optimized, 2049 .mod_optimized, 2050 .neg_optimized, 2051 .cmp_lt_optimized, 2052 .cmp_lte_optimized, 2053 .cmp_eq_optimized, 2054 .cmp_gte_optimized, 2055 .cmp_gt_optimized, 2056 .cmp_neq_optimized, 2057 .cmp_vector_optimized, 2058 .reduce_optimized, 2059 .int_from_float_optimized, 2060 => return func.fail("TODO implement optimized float mode", .{}), 2061 2062 .add_safe, 2063 .sub_safe, 2064 .mul_safe, 2065 => return func.fail("TODO implement safety_checked_instructions", .{}), 2066 2067 .work_item_id, 2068 .work_group_size, 2069 .work_group_id, 2070 => unreachable, 2071 }; 2072 } 2073 2074 fn genBody(func: *CodeGen, body: []const Air.Inst.Index) InnerError!void { 2075 const mod = func.bin_file.base.comp.module.?; 2076 const ip = &mod.intern_pool; 2077 2078 for (body) |inst| { 2079 if (func.liveness.isUnused(inst) and !func.air.mustLower(inst, ip)) { 2080 continue; 2081 } 2082 const old_bookkeeping_value = func.air_bookkeeping; 2083 try func.currentBranch().values.ensureUnusedCapacity(func.gpa, Liveness.bpi); 2084 try func.genInst(inst); 2085 2086 if (builtin.mode == .Debug and func.air_bookkeeping < old_bookkeeping_value + 1) { 2087 std.debug.panic("Missing call to `finishAir` in AIR instruction %{d} ('{}')", .{ 2088 inst, 2089 func.air.instructions.items(.tag)[@intFromEnum(inst)], 2090 }); 2091 } 2092 } 2093 } 2094 2095 fn airRet(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 2096 const mod = func.bin_file.base.comp.module.?; 2097 const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 2098 const operand = try func.resolveInst(un_op); 2099 const fn_info = mod.typeToFunc(func.decl.ty).?; 2100 const ret_ty = Type.fromInterned(fn_info.return_type); 2101 2102 // result must be stored in the stack and we return a pointer 2103 // to the stack instead 2104 if (func.return_value != .none) { 2105 try func.store(func.return_value, operand, ret_ty, 0); 2106 } else if (fn_info.cc == .C and ret_ty.hasRuntimeBitsIgnoreComptime(mod)) { 2107 switch (ret_ty.zigTypeTag(mod)) { 2108 // Aggregate types can be lowered as a singular value 2109 .Struct, .Union => { 2110 const scalar_type = abi.scalarType(ret_ty, mod); 2111 try func.emitWValue(operand); 2112 const opcode = buildOpcode(.{ 2113 .op = .load, 2114 .width = @as(u8, @intCast(scalar_type.abiSize(mod) * 8)), 2115 .signedness = if (scalar_type.isSignedInt(mod)) .signed else .unsigned, 2116 .valtype1 = typeToValtype(scalar_type, mod), 2117 }); 2118 try func.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{ 2119 .offset = operand.offset(), 2120 .alignment = @intCast(scalar_type.abiAlignment(mod).toByteUnitsOptional().?), 2121 }); 2122 }, 2123 else => try func.emitWValue(operand), 2124 } 2125 } else { 2126 if (!ret_ty.hasRuntimeBitsIgnoreComptime(mod) and ret_ty.isError(mod)) { 2127 try func.addImm32(0); 2128 } else { 2129 try func.emitWValue(operand); 2130 } 2131 } 2132 try func.restoreStackPointer(); 2133 try func.addTag(.@"return"); 2134 2135 func.finishAir(inst, .none, &.{un_op}); 2136 } 2137 2138 fn airRetPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 2139 const mod = func.bin_file.base.comp.module.?; 2140 const child_type = func.typeOfIndex(inst).childType(mod); 2141 2142 const result = result: { 2143 if (!child_type.isFnOrHasRuntimeBitsIgnoreComptime(mod)) { 2144 break :result try func.allocStack(Type.usize); // create pointer to void 2145 } 2146 2147 const fn_info = mod.typeToFunc(func.decl.ty).?; 2148 if (firstParamSRet(fn_info.cc, Type.fromInterned(fn_info.return_type), mod)) { 2149 break :result func.return_value; 2150 } 2151 2152 break :result try func.allocStackPtr(inst); 2153 }; 2154 2155 func.finishAir(inst, result, &.{}); 2156 } 2157 2158 fn airRetLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 2159 const mod = func.bin_file.base.comp.module.?; 2160 const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 2161 const operand = try func.resolveInst(un_op); 2162 const ret_ty = func.typeOf(un_op).childType(mod); 2163 2164 const fn_info = mod.typeToFunc(func.decl.ty).?; 2165 if (!ret_ty.hasRuntimeBitsIgnoreComptime(mod)) { 2166 if (ret_ty.isError(mod)) { 2167 try func.addImm32(0); 2168 } 2169 } else if (!firstParamSRet(fn_info.cc, Type.fromInterned(fn_info.return_type), mod)) { 2170 // leave on the stack 2171 _ = try func.load(operand, ret_ty, 0); 2172 } 2173 2174 try func.restoreStackPointer(); 2175 try func.addTag(.@"return"); 2176 return func.finishAir(inst, .none, &.{un_op}); 2177 } 2178 2179 fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) InnerError!void { 2180 if (modifier == .always_tail) return func.fail("TODO implement tail calls for wasm", .{}); 2181 const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 2182 const extra = func.air.extraData(Air.Call, pl_op.payload); 2183 const args = @as([]const Air.Inst.Ref, @ptrCast(func.air.extra[extra.end..][0..extra.data.args_len])); 2184 const ty = func.typeOf(pl_op.operand); 2185 2186 const mod = func.bin_file.base.comp.module.?; 2187 const ip = &mod.intern_pool; 2188 const fn_ty = switch (ty.zigTypeTag(mod)) { 2189 .Fn => ty, 2190 .Pointer => ty.childType(mod), 2191 else => unreachable, 2192 }; 2193 const ret_ty = fn_ty.fnReturnType(mod); 2194 const fn_info = mod.typeToFunc(fn_ty).?; 2195 const first_param_sret = firstParamSRet(fn_info.cc, Type.fromInterned(fn_info.return_type), mod); 2196 2197 const callee: ?InternPool.DeclIndex = blk: { 2198 const func_val = (try func.air.value(pl_op.operand, mod)) orelse break :blk null; 2199 2200 if (func_val.getFunction(mod)) |function| { 2201 _ = try func.bin_file.getOrCreateAtomForDecl(function.owner_decl); 2202 break :blk function.owner_decl; 2203 } else if (func_val.getExternFunc(mod)) |extern_func| { 2204 const ext_decl = mod.declPtr(extern_func.decl); 2205 const ext_info = mod.typeToFunc(ext_decl.ty).?; 2206 var func_type = try genFunctype(func.gpa, ext_info.cc, ext_info.param_types.get(ip), Type.fromInterned(ext_info.return_type), mod); 2207 defer func_type.deinit(func.gpa); 2208 const atom_index = try func.bin_file.getOrCreateAtomForDecl(extern_func.decl); 2209 const atom = func.bin_file.getAtomPtr(atom_index); 2210 const type_index = try func.bin_file.storeDeclType(extern_func.decl, func_type); 2211 try func.bin_file.addOrUpdateImport( 2212 mod.intern_pool.stringToSlice(ext_decl.name), 2213 atom.getSymbolIndex().?, 2214 mod.intern_pool.stringToSliceUnwrap(ext_decl.getOwnedExternFunc(mod).?.lib_name), 2215 type_index, 2216 ); 2217 break :blk extern_func.decl; 2218 } else switch (mod.intern_pool.indexToKey(func_val.ip_index)) { 2219 .ptr => |ptr| switch (ptr.addr) { 2220 .decl => |decl| { 2221 _ = try func.bin_file.getOrCreateAtomForDecl(decl); 2222 break :blk decl; 2223 }, 2224 else => {}, 2225 }, 2226 else => {}, 2227 } 2228 return func.fail("Expected a function, but instead found type '{}'", .{func_val.tag()}); 2229 }; 2230 2231 const sret = if (first_param_sret) blk: { 2232 const sret_local = try func.allocStack(ret_ty); 2233 try func.lowerToStack(sret_local); 2234 break :blk sret_local; 2235 } else WValue{ .none = {} }; 2236 2237 for (args) |arg| { 2238 const arg_val = try func.resolveInst(arg); 2239 2240 const arg_ty = func.typeOf(arg); 2241 if (!arg_ty.hasRuntimeBitsIgnoreComptime(mod)) continue; 2242 2243 try func.lowerArg(mod.typeToFunc(fn_ty).?.cc, arg_ty, arg_val); 2244 } 2245 2246 if (callee) |direct| { 2247 const atom_index = func.bin_file.decls.get(direct).?; 2248 try func.addLabel(.call, func.bin_file.getAtom(atom_index).sym_index); 2249 } else { 2250 // in this case we call a function pointer 2251 // so load its value onto the stack 2252 std.debug.assert(ty.zigTypeTag(mod) == .Pointer); 2253 const operand = try func.resolveInst(pl_op.operand); 2254 try func.emitWValue(operand); 2255 2256 var fn_type = try genFunctype(func.gpa, fn_info.cc, fn_info.param_types.get(ip), Type.fromInterned(fn_info.return_type), mod); 2257 defer fn_type.deinit(func.gpa); 2258 2259 const fn_type_index = try func.bin_file.putOrGetFuncType(fn_type); 2260 try func.addLabel(.call_indirect, fn_type_index); 2261 } 2262 2263 const result_value = result_value: { 2264 if (!ret_ty.hasRuntimeBitsIgnoreComptime(mod) and !ret_ty.isError(mod)) { 2265 break :result_value WValue{ .none = {} }; 2266 } else if (ret_ty.isNoReturn(mod)) { 2267 try func.addTag(.@"unreachable"); 2268 break :result_value WValue{ .none = {} }; 2269 } else if (first_param_sret) { 2270 break :result_value sret; 2271 // TODO: Make this less fragile and optimize 2272 } else if (mod.typeToFunc(fn_ty).?.cc == .C and ret_ty.zigTypeTag(mod) == .Struct or ret_ty.zigTypeTag(mod) == .Union) { 2273 const result_local = try func.allocLocal(ret_ty); 2274 try func.addLabel(.local_set, result_local.local.value); 2275 const scalar_type = abi.scalarType(ret_ty, mod); 2276 const result = try func.allocStack(scalar_type); 2277 try func.store(result, result_local, scalar_type, 0); 2278 break :result_value result; 2279 } else { 2280 const result_local = try func.allocLocal(ret_ty); 2281 try func.addLabel(.local_set, result_local.local.value); 2282 break :result_value result_local; 2283 } 2284 }; 2285 2286 var bt = try func.iterateBigTomb(inst, 1 + args.len); 2287 bt.feed(pl_op.operand); 2288 for (args) |arg| bt.feed(arg); 2289 return bt.finishAir(result_value); 2290 } 2291 2292 fn airAlloc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 2293 const value = try func.allocStackPtr(inst); 2294 func.finishAir(inst, value, &.{}); 2295 } 2296 2297 fn airStore(func: *CodeGen, inst: Air.Inst.Index, safety: bool) InnerError!void { 2298 const mod = func.bin_file.base.comp.module.?; 2299 if (safety) { 2300 // TODO if the value is undef, write 0xaa bytes to dest 2301 } else { 2302 // TODO if the value is undef, don't lower this instruction 2303 } 2304 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 2305 2306 const lhs = try func.resolveInst(bin_op.lhs); 2307 const rhs = try func.resolveInst(bin_op.rhs); 2308 const ptr_ty = func.typeOf(bin_op.lhs); 2309 const ptr_info = ptr_ty.ptrInfo(mod); 2310 const ty = ptr_ty.childType(mod); 2311 2312 if (ptr_info.packed_offset.host_size == 0) { 2313 try func.store(lhs, rhs, ty, 0); 2314 } else { 2315 // at this point we have a non-natural alignment, we must 2316 // load the value, and then shift+or the rhs into the result location. 2317 const int_elem_ty = try mod.intType(.unsigned, ptr_info.packed_offset.host_size * 8); 2318 2319 if (isByRef(int_elem_ty, mod)) { 2320 return func.fail("TODO: airStore for pointers to bitfields with backing type larger than 64bits", .{}); 2321 } 2322 2323 var mask = @as(u64, @intCast((@as(u65, 1) << @as(u7, @intCast(ty.bitSize(mod)))) - 1)); 2324 mask <<= @as(u6, @intCast(ptr_info.packed_offset.bit_offset)); 2325 mask ^= ~@as(u64, 0); 2326 const shift_val = if (ptr_info.packed_offset.host_size <= 4) 2327 WValue{ .imm32 = ptr_info.packed_offset.bit_offset } 2328 else 2329 WValue{ .imm64 = ptr_info.packed_offset.bit_offset }; 2330 const mask_val = if (ptr_info.packed_offset.host_size <= 4) 2331 WValue{ .imm32 = @as(u32, @truncate(mask)) } 2332 else 2333 WValue{ .imm64 = mask }; 2334 2335 try func.emitWValue(lhs); 2336 const loaded = try func.load(lhs, int_elem_ty, 0); 2337 const anded = try func.binOp(loaded, mask_val, int_elem_ty, .@"and"); 2338 const extended_value = try func.intcast(rhs, ty, int_elem_ty); 2339 const shifted_value = if (ptr_info.packed_offset.bit_offset > 0) shifted: { 2340 break :shifted try func.binOp(extended_value, shift_val, int_elem_ty, .shl); 2341 } else extended_value; 2342 const result = try func.binOp(anded, shifted_value, int_elem_ty, .@"or"); 2343 // lhs is still on the stack 2344 try func.store(.stack, result, int_elem_ty, lhs.offset()); 2345 } 2346 2347 func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); 2348 } 2349 2350 fn store(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerError!void { 2351 assert(!(lhs != .stack and rhs == .stack)); 2352 const mod = func.bin_file.base.comp.module.?; 2353 const abi_size = ty.abiSize(mod); 2354 switch (ty.zigTypeTag(mod)) { 2355 .ErrorUnion => { 2356 const pl_ty = ty.errorUnionPayload(mod); 2357 if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) { 2358 return func.store(lhs, rhs, Type.anyerror, 0); 2359 } 2360 2361 const len = @as(u32, @intCast(abi_size)); 2362 return func.memcpy(lhs, rhs, .{ .imm32 = len }); 2363 }, 2364 .Optional => { 2365 if (ty.isPtrLikeOptional(mod)) { 2366 return func.store(lhs, rhs, Type.usize, 0); 2367 } 2368 const pl_ty = ty.optionalChild(mod); 2369 if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) { 2370 return func.store(lhs, rhs, Type.u8, 0); 2371 } 2372 if (pl_ty.zigTypeTag(mod) == .ErrorSet) { 2373 return func.store(lhs, rhs, Type.anyerror, 0); 2374 } 2375 2376 const len = @as(u32, @intCast(abi_size)); 2377 return func.memcpy(lhs, rhs, .{ .imm32 = len }); 2378 }, 2379 .Struct, .Array, .Union => if (isByRef(ty, mod)) { 2380 const len = @as(u32, @intCast(abi_size)); 2381 return func.memcpy(lhs, rhs, .{ .imm32 = len }); 2382 }, 2383 .Vector => switch (determineSimdStoreStrategy(ty, mod)) { 2384 .unrolled => { 2385 const len: u32 = @intCast(abi_size); 2386 return func.memcpy(lhs, rhs, .{ .imm32 = len }); 2387 }, 2388 .direct => { 2389 try func.emitWValue(lhs); 2390 try func.lowerToStack(rhs); 2391 // TODO: Add helper functions for simd opcodes 2392 const extra_index: u32 = @intCast(func.mir_extra.items.len); 2393 // stores as := opcode, offset, alignment (opcode::memarg) 2394 try func.mir_extra.appendSlice(func.gpa, &[_]u32{ 2395 std.wasm.simdOpcode(.v128_store), 2396 offset + lhs.offset(), 2397 @intCast(ty.abiAlignment(mod).toByteUnits(0)), 2398 }); 2399 return func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } }); 2400 }, 2401 }, 2402 .Pointer => { 2403 if (ty.isSlice(mod)) { 2404 // store pointer first 2405 // lower it to the stack so we do not have to store rhs into a local first 2406 try func.emitWValue(lhs); 2407 const ptr_local = try func.load(rhs, Type.usize, 0); 2408 try func.store(.{ .stack = {} }, ptr_local, Type.usize, 0 + lhs.offset()); 2409 2410 // retrieve length from rhs, and store that alongside lhs as well 2411 try func.emitWValue(lhs); 2412 const len_local = try func.load(rhs, Type.usize, func.ptrSize()); 2413 try func.store(.{ .stack = {} }, len_local, Type.usize, func.ptrSize() + lhs.offset()); 2414 return; 2415 } 2416 }, 2417 .Int, .Float => if (abi_size > 8 and abi_size <= 16) { 2418 try func.emitWValue(lhs); 2419 const lsb = try func.load(rhs, Type.u64, 0); 2420 try func.store(.{ .stack = {} }, lsb, Type.u64, 0 + lhs.offset()); 2421 2422 try func.emitWValue(lhs); 2423 const msb = try func.load(rhs, Type.u64, 8); 2424 try func.store(.{ .stack = {} }, msb, Type.u64, 8 + lhs.offset()); 2425 return; 2426 } else if (abi_size > 16) { 2427 try func.memcpy(lhs, rhs, .{ .imm32 = @as(u32, @intCast(ty.abiSize(mod))) }); 2428 }, 2429 else => if (abi_size > 8) { 2430 return func.fail("TODO: `store` for type `{}` with abisize `{d}`", .{ 2431 ty.fmt(func.bin_file.base.comp.module.?), 2432 abi_size, 2433 }); 2434 }, 2435 } 2436 try func.emitWValue(lhs); 2437 // In this case we're actually interested in storing the stack position 2438 // into lhs, so we calculate that and emit that instead 2439 try func.lowerToStack(rhs); 2440 2441 const valtype = typeToValtype(ty, mod); 2442 const opcode = buildOpcode(.{ 2443 .valtype1 = valtype, 2444 .width = @as(u8, @intCast(abi_size * 8)), 2445 .op = .store, 2446 }); 2447 2448 // store rhs value at stack pointer's location in memory 2449 try func.addMemArg( 2450 Mir.Inst.Tag.fromOpcode(opcode), 2451 .{ 2452 .offset = offset + lhs.offset(), 2453 .alignment = @intCast(ty.abiAlignment(mod).toByteUnitsOptional().?), 2454 }, 2455 ); 2456 } 2457 2458 fn airLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 2459 const mod = func.bin_file.base.comp.module.?; 2460 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 2461 const operand = try func.resolveInst(ty_op.operand); 2462 const ty = ty_op.ty.toType(); 2463 const ptr_ty = func.typeOf(ty_op.operand); 2464 const ptr_info = ptr_ty.ptrInfo(mod); 2465 2466 if (!ty.hasRuntimeBitsIgnoreComptime(mod)) return func.finishAir(inst, .none, &.{ty_op.operand}); 2467 2468 const result = result: { 2469 if (isByRef(ty, mod)) { 2470 const new_local = try func.allocStack(ty); 2471 try func.store(new_local, operand, ty, 0); 2472 break :result new_local; 2473 } 2474 2475 if (ptr_info.packed_offset.host_size == 0) { 2476 const stack_loaded = try func.load(operand, ty, 0); 2477 break :result try stack_loaded.toLocal(func, ty); 2478 } 2479 2480 // at this point we have a non-natural alignment, we must 2481 // shift the value to obtain the correct bit. 2482 const int_elem_ty = try mod.intType(.unsigned, ptr_info.packed_offset.host_size * 8); 2483 const shift_val = if (ptr_info.packed_offset.host_size <= 4) 2484 WValue{ .imm32 = ptr_info.packed_offset.bit_offset } 2485 else if (ptr_info.packed_offset.host_size <= 8) 2486 WValue{ .imm64 = ptr_info.packed_offset.bit_offset } 2487 else 2488 return func.fail("TODO: airLoad where ptr to bitfield exceeds 64 bits", .{}); 2489 2490 const stack_loaded = try func.load(operand, int_elem_ty, 0); 2491 const shifted = try func.binOp(stack_loaded, shift_val, int_elem_ty, .shr); 2492 const result = try func.trunc(shifted, ty, int_elem_ty); 2493 // const wrapped = try func.wrapOperand(shifted, ty); 2494 break :result try result.toLocal(func, ty); 2495 }; 2496 func.finishAir(inst, result, &.{ty_op.operand}); 2497 } 2498 2499 /// Loads an operand from the linear memory section. 2500 /// NOTE: Leaves the value on the stack. 2501 fn load(func: *CodeGen, operand: WValue, ty: Type, offset: u32) InnerError!WValue { 2502 const mod = func.bin_file.base.comp.module.?; 2503 // load local's value from memory by its stack position 2504 try func.emitWValue(operand); 2505 2506 if (ty.zigTypeTag(mod) == .Vector) { 2507 // TODO: Add helper functions for simd opcodes 2508 const extra_index = @as(u32, @intCast(func.mir_extra.items.len)); 2509 // stores as := opcode, offset, alignment (opcode::memarg) 2510 try func.mir_extra.appendSlice(func.gpa, &[_]u32{ 2511 std.wasm.simdOpcode(.v128_load), 2512 offset + operand.offset(), 2513 @intCast(ty.abiAlignment(mod).toByteUnitsOptional().?), 2514 }); 2515 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } }); 2516 return WValue{ .stack = {} }; 2517 } 2518 2519 const abi_size = @as(u8, @intCast(ty.abiSize(mod))); 2520 const opcode = buildOpcode(.{ 2521 .valtype1 = typeToValtype(ty, mod), 2522 .width = abi_size * 8, 2523 .op = .load, 2524 .signedness = .unsigned, 2525 }); 2526 2527 try func.addMemArg( 2528 Mir.Inst.Tag.fromOpcode(opcode), 2529 .{ 2530 .offset = offset + operand.offset(), 2531 .alignment = @intCast(ty.abiAlignment(mod).toByteUnitsOptional().?), 2532 }, 2533 ); 2534 2535 return WValue{ .stack = {} }; 2536 } 2537 2538 fn airArg(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 2539 const mod = func.bin_file.base.comp.module.?; 2540 const arg_index = func.arg_index; 2541 const arg = func.args[arg_index]; 2542 const cc = mod.typeToFunc(func.decl.ty).?.cc; 2543 const arg_ty = func.typeOfIndex(inst); 2544 if (cc == .C) { 2545 const arg_classes = abi.classifyType(arg_ty, mod); 2546 for (arg_classes) |class| { 2547 if (class != .none) { 2548 func.arg_index += 1; 2549 } 2550 } 2551 2552 // When we have an argument that's passed using more than a single parameter, 2553 // we combine them into a single stack value 2554 if (arg_classes[0] == .direct and arg_classes[1] == .direct) { 2555 if (arg_ty.zigTypeTag(mod) != .Int and arg_ty.zigTypeTag(mod) != .Float) { 2556 return func.fail( 2557 "TODO: Implement C-ABI argument for type '{}'", 2558 .{arg_ty.fmt(func.bin_file.base.comp.module.?)}, 2559 ); 2560 } 2561 const result = try func.allocStack(arg_ty); 2562 try func.store(result, arg, Type.u64, 0); 2563 try func.store(result, func.args[arg_index + 1], Type.u64, 8); 2564 return func.finishAir(inst, result, &.{}); 2565 } 2566 } else { 2567 func.arg_index += 1; 2568 } 2569 2570 switch (func.debug_output) { 2571 .dwarf => |dwarf| { 2572 const src_index = func.air.instructions.items(.data)[@intFromEnum(inst)].arg.src_index; 2573 const name = mod.getParamName(func.func_index, src_index); 2574 try dwarf.genArgDbgInfo(name, arg_ty, mod.funcOwnerDeclIndex(func.func_index), .{ 2575 .wasm_local = arg.local.value, 2576 }); 2577 }, 2578 else => {}, 2579 } 2580 2581 func.finishAir(inst, arg, &.{}); 2582 } 2583 2584 fn airBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void { 2585 const mod = func.bin_file.base.comp.module.?; 2586 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 2587 const lhs = try func.resolveInst(bin_op.lhs); 2588 const rhs = try func.resolveInst(bin_op.rhs); 2589 const lhs_ty = func.typeOf(bin_op.lhs); 2590 const rhs_ty = func.typeOf(bin_op.rhs); 2591 2592 // For certain operations, such as shifting, the types are different. 2593 // When converting this to a WebAssembly type, they *must* match to perform 2594 // an operation. For this reason we verify if the WebAssembly type is different, in which 2595 // case we first coerce the operands to the same type before performing the operation. 2596 // For big integers we can ignore this as we will call into compiler-rt which handles this. 2597 const result = switch (op) { 2598 .shr, .shl => res: { 2599 const lhs_wasm_bits = toWasmBits(@as(u16, @intCast(lhs_ty.bitSize(mod)))) orelse { 2600 return func.fail("TODO: implement '{s}' for types larger than 128 bits", .{@tagName(op)}); 2601 }; 2602 const rhs_wasm_bits = toWasmBits(@as(u16, @intCast(rhs_ty.bitSize(mod)))).?; 2603 const new_rhs = if (lhs_wasm_bits != rhs_wasm_bits and lhs_wasm_bits != 128) blk: { 2604 const tmp = try func.intcast(rhs, rhs_ty, lhs_ty); 2605 break :blk try tmp.toLocal(func, lhs_ty); 2606 } else rhs; 2607 const stack_result = try func.binOp(lhs, new_rhs, lhs_ty, op); 2608 break :res try stack_result.toLocal(func, lhs_ty); 2609 }, 2610 else => res: { 2611 const stack_result = try func.binOp(lhs, rhs, lhs_ty, op); 2612 break :res try stack_result.toLocal(func, lhs_ty); 2613 }, 2614 }; 2615 2616 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 2617 } 2618 2619 /// Performs a binary operation on the given `WValue`'s 2620 /// NOTE: THis leaves the value on top of the stack. 2621 fn binOp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue { 2622 const mod = func.bin_file.base.comp.module.?; 2623 assert(!(lhs != .stack and rhs == .stack)); 2624 2625 if (ty.isAnyFloat()) { 2626 const float_op = FloatOp.fromOp(op); 2627 return func.floatOp(float_op, ty, &.{ lhs, rhs }); 2628 } 2629 2630 if (isByRef(ty, mod)) { 2631 if (ty.zigTypeTag(mod) == .Int) { 2632 return func.binOpBigInt(lhs, rhs, ty, op); 2633 } else { 2634 return func.fail( 2635 "TODO: Implement binary operation for type: {}", 2636 .{ty.fmt(func.bin_file.base.comp.module.?)}, 2637 ); 2638 } 2639 } 2640 2641 const opcode: wasm.Opcode = buildOpcode(.{ 2642 .op = op, 2643 .valtype1 = typeToValtype(ty, mod), 2644 .signedness = if (ty.isSignedInt(mod)) .signed else .unsigned, 2645 }); 2646 try func.emitWValue(lhs); 2647 try func.emitWValue(rhs); 2648 2649 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); 2650 2651 return WValue{ .stack = {} }; 2652 } 2653 2654 fn binOpBigInt(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue { 2655 const mod = func.bin_file.base.comp.module.?; 2656 const int_info = ty.intInfo(mod); 2657 if (int_info.bits > 128) { 2658 return func.fail("TODO: Implement binary operation for big integers larger than 128 bits", .{}); 2659 } 2660 2661 switch (op) { 2662 .mul => return func.callIntrinsic("__multi3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }), 2663 .div => switch (int_info.signedness) { 2664 .signed => return func.callIntrinsic("__udivti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }), 2665 .unsigned => return func.callIntrinsic("__divti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }), 2666 }, 2667 .rem => return func.callIntrinsic("__umodti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }), 2668 .shr => return func.callIntrinsic("__lshrti3", &.{ ty.toIntern(), .i32_type }, ty, &.{ lhs, rhs }), 2669 .shl => return func.callIntrinsic("__ashlti3", &.{ ty.toIntern(), .i32_type }, ty, &.{ lhs, rhs }), 2670 .xor => { 2671 const result = try func.allocStack(ty); 2672 try func.emitWValue(result); 2673 const lhs_high_bit = try func.load(lhs, Type.u64, 0); 2674 const rhs_high_bit = try func.load(rhs, Type.u64, 0); 2675 const xor_high_bit = try func.binOp(lhs_high_bit, rhs_high_bit, Type.u64, .xor); 2676 try func.store(.stack, xor_high_bit, Type.u64, result.offset()); 2677 2678 try func.emitWValue(result); 2679 const lhs_low_bit = try func.load(lhs, Type.u64, 8); 2680 const rhs_low_bit = try func.load(rhs, Type.u64, 8); 2681 const xor_low_bit = try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, .xor); 2682 try func.store(.stack, xor_low_bit, Type.u64, result.offset() + 8); 2683 return result; 2684 }, 2685 .add, .sub => { 2686 const result = try func.allocStack(ty); 2687 var lhs_high_bit = try (try func.load(lhs, Type.u64, 0)).toLocal(func, Type.u64); 2688 defer lhs_high_bit.free(func); 2689 var rhs_high_bit = try (try func.load(rhs, Type.u64, 0)).toLocal(func, Type.u64); 2690 defer rhs_high_bit.free(func); 2691 var high_op_res = try (try func.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op)).toLocal(func, Type.u64); 2692 defer high_op_res.free(func); 2693 2694 const lhs_low_bit = try func.load(lhs, Type.u64, 8); 2695 const rhs_low_bit = try func.load(rhs, Type.u64, 8); 2696 const low_op_res = try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, op); 2697 2698 const lt = if (op == .add) blk: { 2699 break :blk try func.cmp(high_op_res, rhs_high_bit, Type.u64, .lt); 2700 } else if (op == .sub) blk: { 2701 break :blk try func.cmp(lhs_high_bit, rhs_high_bit, Type.u64, .lt); 2702 } else unreachable; 2703 const tmp = try func.intcast(lt, Type.u32, Type.u64); 2704 var tmp_op = try (try func.binOp(low_op_res, tmp, Type.u64, op)).toLocal(func, Type.u64); 2705 defer tmp_op.free(func); 2706 2707 try func.store(result, high_op_res, Type.u64, 0); 2708 try func.store(result, tmp_op, Type.u64, 8); 2709 return result; 2710 }, 2711 else => return func.fail("TODO: Implement binary operation for big integers: '{s}'", .{@tagName(op)}), 2712 } 2713 } 2714 2715 const FloatOp = enum { 2716 add, 2717 ceil, 2718 cos, 2719 div, 2720 exp, 2721 exp2, 2722 fabs, 2723 floor, 2724 fma, 2725 fmax, 2726 fmin, 2727 fmod, 2728 log, 2729 log10, 2730 log2, 2731 mul, 2732 neg, 2733 round, 2734 sin, 2735 sqrt, 2736 sub, 2737 tan, 2738 trunc, 2739 2740 pub fn fromOp(op: Op) FloatOp { 2741 return switch (op) { 2742 .add => .add, 2743 .ceil => .ceil, 2744 .div => .div, 2745 .abs => .fabs, 2746 .floor => .floor, 2747 .max => .fmax, 2748 .min => .fmin, 2749 .mul => .mul, 2750 .neg => .neg, 2751 .nearest => .round, 2752 .sqrt => .sqrt, 2753 .sub => .sub, 2754 .trunc => .trunc, 2755 else => unreachable, 2756 }; 2757 } 2758 2759 pub fn toOp(float_op: FloatOp) ?Op { 2760 return switch (float_op) { 2761 .add => .add, 2762 .ceil => .ceil, 2763 .div => .div, 2764 .fabs => .abs, 2765 .floor => .floor, 2766 .fmax => .max, 2767 .fmin => .min, 2768 .mul => .mul, 2769 .neg => .neg, 2770 .round => .nearest, 2771 .sqrt => .sqrt, 2772 .sub => .sub, 2773 .trunc => .trunc, 2774 2775 .cos, 2776 .exp, 2777 .exp2, 2778 .fma, 2779 .fmod, 2780 .log, 2781 .log10, 2782 .log2, 2783 .sin, 2784 .tan, 2785 => null, 2786 }; 2787 } 2788 }; 2789 2790 fn airAbs(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 2791 const mod = func.bin_file.base.comp.module.?; 2792 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 2793 const operand = try func.resolveInst(ty_op.operand); 2794 const ty = func.typeOf(ty_op.operand); 2795 const scalar_ty = ty.scalarType(mod); 2796 2797 switch (scalar_ty.zigTypeTag(mod)) { 2798 .Int => if (ty.zigTypeTag(mod) == .Vector) { 2799 return func.fail("TODO implement airAbs for {}", .{ty.fmt(mod)}); 2800 } else { 2801 const int_bits = ty.intInfo(mod).bits; 2802 const wasm_bits = toWasmBits(int_bits) orelse { 2803 return func.fail("TODO: airAbs for signed integers larger than '{d}' bits", .{int_bits}); 2804 }; 2805 2806 const op = try operand.toLocal(func, ty); 2807 2808 try func.emitWValue(op); 2809 switch (wasm_bits) { 2810 32 => { 2811 if (wasm_bits != int_bits) { 2812 try func.addImm32(wasm_bits - int_bits); 2813 try func.addTag(.i32_shl); 2814 } 2815 try func.addImm32(31); 2816 try func.addTag(.i32_shr_s); 2817 2818 const tmp = try func.allocLocal(ty); 2819 try func.addLabel(.local_tee, tmp.local.value); 2820 2821 try func.emitWValue(op); 2822 try func.addTag(.i32_xor); 2823 try func.emitWValue(tmp); 2824 try func.addTag(.i32_sub); 2825 2826 if (int_bits != wasm_bits) { 2827 try func.emitWValue(WValue{ .imm32 = (@as(u32, 1) << @intCast(int_bits)) - 1 }); 2828 try func.addTag(.i32_and); 2829 } 2830 }, 2831 64 => { 2832 if (wasm_bits != int_bits) { 2833 try func.addImm64(wasm_bits - int_bits); 2834 try func.addTag(.i64_shl); 2835 } 2836 try func.addImm64(63); 2837 try func.addTag(.i64_shr_s); 2838 2839 const tmp = try func.allocLocal(ty); 2840 try func.addLabel(.local_tee, tmp.local.value); 2841 2842 try func.emitWValue(op); 2843 try func.addTag(.i64_xor); 2844 try func.emitWValue(tmp); 2845 try func.addTag(.i64_sub); 2846 2847 if (int_bits != wasm_bits) { 2848 try func.emitWValue(WValue{ .imm64 = (@as(u64, 1) << @intCast(int_bits)) - 1 }); 2849 try func.addTag(.i64_and); 2850 } 2851 }, 2852 else => return func.fail("TODO: Implement airAbs for {}", .{ty.fmt(mod)}), 2853 } 2854 2855 const result = try (WValue{ .stack = {} }).toLocal(func, ty); 2856 func.finishAir(inst, result, &.{ty_op.operand}); 2857 }, 2858 .Float => { 2859 const result = try (try func.floatOp(.fabs, ty, &.{operand})).toLocal(func, ty); 2860 func.finishAir(inst, result, &.{ty_op.operand}); 2861 }, 2862 else => unreachable, 2863 } 2864 } 2865 2866 fn airUnaryFloatOp(func: *CodeGen, inst: Air.Inst.Index, op: FloatOp) InnerError!void { 2867 const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 2868 const operand = try func.resolveInst(un_op); 2869 const ty = func.typeOf(un_op); 2870 2871 const result = try (try func.floatOp(op, ty, &.{operand})).toLocal(func, ty); 2872 func.finishAir(inst, result, &.{un_op}); 2873 } 2874 2875 fn floatOp(func: *CodeGen, float_op: FloatOp, ty: Type, args: []const WValue) InnerError!WValue { 2876 const mod = func.bin_file.base.comp.module.?; 2877 if (ty.zigTypeTag(mod) == .Vector) { 2878 return func.fail("TODO: Implement floatOps for vectors", .{}); 2879 } 2880 2881 const float_bits = ty.floatBits(func.target); 2882 2883 if (float_op == .neg) { 2884 return func.floatNeg(ty, args[0]); 2885 } 2886 2887 if (float_bits == 32 or float_bits == 64) { 2888 if (float_op.toOp()) |op| { 2889 for (args) |operand| { 2890 try func.emitWValue(operand); 2891 } 2892 const opcode = buildOpcode(.{ .op = op, .valtype1 = typeToValtype(ty, mod) }); 2893 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); 2894 return .stack; 2895 } 2896 } 2897 2898 var fn_name_buf: [64]u8 = undefined; 2899 const fn_name = switch (float_op) { 2900 .add, 2901 .sub, 2902 .div, 2903 .mul, 2904 => std.fmt.bufPrint(&fn_name_buf, "__{s}{s}f3", .{ 2905 @tagName(float_op), target_util.compilerRtFloatAbbrev(float_bits), 2906 }) catch unreachable, 2907 2908 .ceil, 2909 .cos, 2910 .exp, 2911 .exp2, 2912 .fabs, 2913 .floor, 2914 .fma, 2915 .fmax, 2916 .fmin, 2917 .fmod, 2918 .log, 2919 .log10, 2920 .log2, 2921 .round, 2922 .sin, 2923 .sqrt, 2924 .tan, 2925 .trunc, 2926 => std.fmt.bufPrint(&fn_name_buf, "{s}{s}{s}", .{ 2927 target_util.libcFloatPrefix(float_bits), @tagName(float_op), target_util.libcFloatSuffix(float_bits), 2928 }) catch unreachable, 2929 .neg => unreachable, // handled above 2930 }; 2931 2932 // fma requires three operands 2933 var param_types_buffer: [3]InternPool.Index = .{ ty.ip_index, ty.ip_index, ty.ip_index }; 2934 const param_types = param_types_buffer[0..args.len]; 2935 return func.callIntrinsic(fn_name, param_types, ty, args); 2936 } 2937 2938 /// NOTE: The result value remains on top of the stack. 2939 fn floatNeg(func: *CodeGen, ty: Type, arg: WValue) InnerError!WValue { 2940 const float_bits = ty.floatBits(func.target); 2941 switch (float_bits) { 2942 16 => { 2943 try func.emitWValue(arg); 2944 try func.addImm32(std.math.minInt(i16)); 2945 try func.addTag(.i32_xor); 2946 return .stack; 2947 }, 2948 32, 64 => { 2949 try func.emitWValue(arg); 2950 const val_type: wasm.Valtype = if (float_bits == 32) .f32 else .f64; 2951 const opcode = buildOpcode(.{ .op = .neg, .valtype1 = val_type }); 2952 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); 2953 return .stack; 2954 }, 2955 80, 128 => { 2956 const result = try func.allocStack(ty); 2957 try func.emitWValue(result); 2958 try func.emitWValue(arg); 2959 try func.addMemArg(.i64_load, .{ .offset = 0 + arg.offset(), .alignment = 2 }); 2960 try func.addMemArg(.i64_store, .{ .offset = 0 + result.offset(), .alignment = 2 }); 2961 2962 try func.emitWValue(result); 2963 try func.emitWValue(arg); 2964 try func.addMemArg(.i64_load, .{ .offset = 8 + arg.offset(), .alignment = 2 }); 2965 2966 if (float_bits == 80) { 2967 try func.addImm64(0x8000); 2968 try func.addTag(.i64_xor); 2969 try func.addMemArg(.i64_store16, .{ .offset = 8 + result.offset(), .alignment = 2 }); 2970 } else { 2971 try func.addImm64(0x8000000000000000); 2972 try func.addTag(.i64_xor); 2973 try func.addMemArg(.i64_store, .{ .offset = 8 + result.offset(), .alignment = 2 }); 2974 } 2975 return result; 2976 }, 2977 else => unreachable, 2978 } 2979 } 2980 2981 fn airWrapBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void { 2982 const mod = func.bin_file.base.comp.module.?; 2983 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 2984 2985 const lhs = try func.resolveInst(bin_op.lhs); 2986 const rhs = try func.resolveInst(bin_op.rhs); 2987 const lhs_ty = func.typeOf(bin_op.lhs); 2988 const rhs_ty = func.typeOf(bin_op.rhs); 2989 2990 if (lhs_ty.zigTypeTag(mod) == .Vector or rhs_ty.zigTypeTag(mod) == .Vector) { 2991 return func.fail("TODO: Implement wrapping arithmetic for vectors", .{}); 2992 } 2993 2994 // For certain operations, such as shifting, the types are different. 2995 // When converting this to a WebAssembly type, they *must* match to perform 2996 // an operation. For this reason we verify if the WebAssembly type is different, in which 2997 // case we first coerce the operands to the same type before performing the operation. 2998 // For big integers we can ignore this as we will call into compiler-rt which handles this. 2999 const result = switch (op) { 3000 .shr, .shl => res: { 3001 const lhs_wasm_bits = toWasmBits(@as(u16, @intCast(lhs_ty.bitSize(mod)))) orelse { 3002 return func.fail("TODO: implement '{s}' for types larger than 128 bits", .{@tagName(op)}); 3003 }; 3004 const rhs_wasm_bits = toWasmBits(@as(u16, @intCast(rhs_ty.bitSize(mod)))).?; 3005 const new_rhs = if (lhs_wasm_bits != rhs_wasm_bits and lhs_wasm_bits != 128) blk: { 3006 const tmp = try func.intcast(rhs, rhs_ty, lhs_ty); 3007 break :blk try tmp.toLocal(func, lhs_ty); 3008 } else rhs; 3009 const stack_result = try func.wrapBinOp(lhs, new_rhs, lhs_ty, op); 3010 break :res try stack_result.toLocal(func, lhs_ty); 3011 }, 3012 else => res: { 3013 const stack_result = try func.wrapBinOp(lhs, rhs, lhs_ty, op); 3014 break :res try stack_result.toLocal(func, lhs_ty); 3015 }, 3016 }; 3017 3018 return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 3019 } 3020 3021 /// Performs a wrapping binary operation. 3022 /// Asserts rhs is not a stack value when lhs also isn't. 3023 /// NOTE: Leaves the result on the stack when its Type is <= 64 bits 3024 fn wrapBinOp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue { 3025 const bin_local = try func.binOp(lhs, rhs, ty, op); 3026 return func.wrapOperand(bin_local, ty); 3027 } 3028 3029 /// Wraps an operand based on a given type's bitsize. 3030 /// Asserts `Type` is <= 128 bits. 3031 /// NOTE: When the Type is <= 64 bits, leaves the value on top of the stack. 3032 fn wrapOperand(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue { 3033 const mod = func.bin_file.base.comp.module.?; 3034 assert(ty.abiSize(mod) <= 16); 3035 const bitsize = @as(u16, @intCast(ty.bitSize(mod))); 3036 const wasm_bits = toWasmBits(bitsize) orelse { 3037 return func.fail("TODO: Implement wrapOperand for bitsize '{d}'", .{bitsize}); 3038 }; 3039 3040 if (wasm_bits == bitsize) return operand; 3041 3042 if (wasm_bits == 128) { 3043 assert(operand != .stack); 3044 const lsb = try func.load(operand, Type.u64, 8); 3045 3046 const result_ptr = try func.allocStack(ty); 3047 try func.emitWValue(result_ptr); 3048 try func.store(.{ .stack = {} }, lsb, Type.u64, 8 + result_ptr.offset()); 3049 const result = (@as(u64, 1) << @as(u6, @intCast(64 - (wasm_bits - bitsize)))) - 1; 3050 try func.emitWValue(result_ptr); 3051 _ = try func.load(operand, Type.u64, 0); 3052 try func.addImm64(result); 3053 try func.addTag(.i64_and); 3054 try func.addMemArg(.i64_store, .{ .offset = result_ptr.offset(), .alignment = 8 }); 3055 return result_ptr; 3056 } 3057 3058 const result = (@as(u64, 1) << @as(u6, @intCast(bitsize))) - 1; 3059 try func.emitWValue(operand); 3060 if (bitsize <= 32) { 3061 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(result))))); 3062 try func.addTag(.i32_and); 3063 } else if (bitsize <= 64) { 3064 try func.addImm64(result); 3065 try func.addTag(.i64_and); 3066 } else unreachable; 3067 3068 return WValue{ .stack = {} }; 3069 } 3070 3071 fn lowerParentPtr(func: *CodeGen, ptr_val: Value, offset: u32) InnerError!WValue { 3072 const mod = func.bin_file.base.comp.module.?; 3073 const ptr = mod.intern_pool.indexToKey(ptr_val.ip_index).ptr; 3074 switch (ptr.addr) { 3075 .decl => |decl_index| { 3076 return func.lowerParentPtrDecl(ptr_val, decl_index, offset); 3077 }, 3078 .anon_decl => |ad| return func.lowerAnonDeclRef(ad, offset), 3079 .mut_decl => |mut_decl| { 3080 const decl_index = mut_decl.decl; 3081 return func.lowerParentPtrDecl(ptr_val, decl_index, offset); 3082 }, 3083 .eu_payload => |tag| return func.fail("TODO: Implement lowerParentPtr for {}", .{tag}), 3084 .int => |base| return func.lowerConstant(Value.fromInterned(base), Type.usize), 3085 .opt_payload => |base_ptr| return func.lowerParentPtr(Value.fromInterned(base_ptr), offset), 3086 .comptime_field => unreachable, 3087 .elem => |elem| { 3088 const index = elem.index; 3089 const elem_type = Type.fromInterned(mod.intern_pool.typeOf(elem.base)).elemType2(mod); 3090 const elem_offset = index * elem_type.abiSize(mod); 3091 return func.lowerParentPtr(Value.fromInterned(elem.base), @as(u32, @intCast(elem_offset + offset))); 3092 }, 3093 .field => |field| { 3094 const parent_ptr_ty = Type.fromInterned(mod.intern_pool.typeOf(field.base)); 3095 const parent_ty = parent_ptr_ty.childType(mod); 3096 const field_index: u32 = @intCast(field.index); 3097 3098 const field_offset = switch (parent_ty.zigTypeTag(mod)) { 3099 .Struct => blk: { 3100 if (mod.typeToPackedStruct(parent_ty)) |struct_type| { 3101 if (Type.fromInterned(ptr.ty).ptrInfo(mod).packed_offset.host_size == 0) 3102 break :blk @divExact(mod.structPackedFieldBitOffset(struct_type, field_index) + parent_ptr_ty.ptrInfo(mod).packed_offset.bit_offset, 8) 3103 else 3104 break :blk 0; 3105 } 3106 break :blk parent_ty.structFieldOffset(field_index, mod); 3107 }, 3108 .Union => switch (parent_ty.containerLayout(mod)) { 3109 .Packed => 0, 3110 else => blk: { 3111 const layout: Module.UnionLayout = parent_ty.unionGetLayout(mod); 3112 if (layout.payload_size == 0) break :blk 0; 3113 if (layout.payload_align.compare(.gt, layout.tag_align)) break :blk 0; 3114 3115 // tag is stored first so calculate offset from where payload starts 3116 break :blk layout.tag_align.forward(layout.tag_size); 3117 }, 3118 }, 3119 .Pointer => switch (parent_ty.ptrSize(mod)) { 3120 .Slice => switch (field.index) { 3121 0 => 0, 3122 1 => func.ptrSize(), 3123 else => unreachable, 3124 }, 3125 else => unreachable, 3126 }, 3127 else => unreachable, 3128 }; 3129 return func.lowerParentPtr(Value.fromInterned(field.base), @as(u32, @intCast(offset + field_offset))); 3130 }, 3131 } 3132 } 3133 3134 fn lowerParentPtrDecl(func: *CodeGen, ptr_val: Value, decl_index: InternPool.DeclIndex, offset: u32) InnerError!WValue { 3135 const mod = func.bin_file.base.comp.module.?; 3136 const decl = mod.declPtr(decl_index); 3137 try mod.markDeclAlive(decl); 3138 const ptr_ty = try mod.singleMutPtrType(decl.ty); 3139 return func.lowerDeclRefValue(.{ .ty = ptr_ty, .val = ptr_val }, decl_index, offset); 3140 } 3141 3142 fn lowerAnonDeclRef( 3143 func: *CodeGen, 3144 anon_decl: InternPool.Key.Ptr.Addr.AnonDecl, 3145 offset: u32, 3146 ) InnerError!WValue { 3147 const mod = func.bin_file.base.comp.module.?; 3148 const decl_val = anon_decl.val; 3149 const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val)); 3150 3151 const is_fn_body = ty.zigTypeTag(mod) == .Fn; 3152 if (!is_fn_body and !ty.hasRuntimeBitsIgnoreComptime(mod)) { 3153 return WValue{ .imm32 = 0xaaaaaaaa }; 3154 } 3155 3156 const decl_align = mod.intern_pool.indexToKey(anon_decl.orig_ty).ptr_type.flags.alignment; 3157 const res = try func.bin_file.lowerAnonDecl(decl_val, decl_align, func.decl.srcLoc(mod)); 3158 switch (res) { 3159 .ok => {}, 3160 .fail => |em| { 3161 func.err_msg = em; 3162 return error.CodegenFail; 3163 }, 3164 } 3165 const target_atom_index = func.bin_file.anon_decls.get(decl_val).?; 3166 const target_sym_index = func.bin_file.getAtom(target_atom_index).getSymbolIndex().?; 3167 if (is_fn_body) { 3168 return WValue{ .function_index = target_sym_index }; 3169 } else if (offset == 0) { 3170 return WValue{ .memory = target_sym_index }; 3171 } else return WValue{ .memory_offset = .{ .pointer = target_sym_index, .offset = offset } }; 3172 } 3173 3174 fn lowerDeclRefValue(func: *CodeGen, tv: TypedValue, decl_index: InternPool.DeclIndex, offset: u32) InnerError!WValue { 3175 const mod = func.bin_file.base.comp.module.?; 3176 if (tv.ty.isSlice(mod)) { 3177 return WValue{ .memory = try func.bin_file.lowerUnnamedConst(tv, decl_index) }; 3178 } 3179 3180 const decl = mod.declPtr(decl_index); 3181 // check if decl is an alias to a function, in which case we 3182 // want to lower the actual decl, rather than the alias itself. 3183 if (decl.val.getFunction(mod)) |func_val| { 3184 if (func_val.owner_decl != decl_index) { 3185 return func.lowerDeclRefValue(tv, func_val.owner_decl, offset); 3186 } 3187 } else if (decl.val.getExternFunc(mod)) |func_val| { 3188 if (func_val.decl != decl_index) { 3189 return func.lowerDeclRefValue(tv, func_val.decl, offset); 3190 } 3191 } 3192 if (decl.ty.zigTypeTag(mod) != .Fn and !decl.ty.hasRuntimeBitsIgnoreComptime(mod)) { 3193 return WValue{ .imm32 = 0xaaaaaaaa }; 3194 } 3195 3196 try mod.markDeclAlive(decl); 3197 const atom_index = try func.bin_file.getOrCreateAtomForDecl(decl_index); 3198 const atom = func.bin_file.getAtom(atom_index); 3199 3200 const target_sym_index = atom.sym_index; 3201 if (decl.ty.zigTypeTag(mod) == .Fn) { 3202 try func.bin_file.addTableFunction(target_sym_index); 3203 return WValue{ .function_index = target_sym_index }; 3204 } else if (offset == 0) { 3205 return WValue{ .memory = target_sym_index }; 3206 } else return WValue{ .memory_offset = .{ .pointer = target_sym_index, .offset = offset } }; 3207 } 3208 3209 /// Converts a signed integer to its 2's complement form and returns 3210 /// an unsigned integer instead. 3211 /// Asserts bitsize <= 64 3212 fn toTwosComplement(value: anytype, bits: u7) std.meta.Int(.unsigned, @typeInfo(@TypeOf(value)).Int.bits) { 3213 const T = @TypeOf(value); 3214 comptime assert(@typeInfo(T) == .Int); 3215 comptime assert(@typeInfo(T).Int.signedness == .signed); 3216 assert(bits <= 64); 3217 const WantedT = std.meta.Int(.unsigned, @typeInfo(T).Int.bits); 3218 if (value >= 0) return @as(WantedT, @bitCast(value)); 3219 const max_value = @as(u64, @intCast((@as(u65, 1) << bits) - 1)); 3220 const flipped = @as(T, @intCast((~-@as(i65, value)) + 1)); 3221 const result = @as(WantedT, @bitCast(flipped)) & max_value; 3222 return @as(WantedT, @intCast(result)); 3223 } 3224 3225 /// This function is intended to assert that `isByRef` returns `false` for `ty`. 3226 /// However such an assertion fails on the behavior tests currently. 3227 fn lowerConstant(func: *CodeGen, val: Value, ty: Type) InnerError!WValue { 3228 const mod = func.bin_file.base.comp.module.?; 3229 // TODO: enable this assertion 3230 //assert(!isByRef(ty, mod)); 3231 const ip = &mod.intern_pool; 3232 if (val.isUndefDeep(mod)) return func.emitUndefined(ty); 3233 3234 switch (ip.indexToKey(val.ip_index)) { 3235 .int_type, 3236 .ptr_type, 3237 .array_type, 3238 .vector_type, 3239 .opt_type, 3240 .anyframe_type, 3241 .error_union_type, 3242 .simple_type, 3243 .struct_type, 3244 .anon_struct_type, 3245 .union_type, 3246 .opaque_type, 3247 .enum_type, 3248 .func_type, 3249 .error_set_type, 3250 .inferred_error_set_type, 3251 => unreachable, // types, not values 3252 3253 .undef => unreachable, // handled above 3254 .simple_value => |simple_value| switch (simple_value) { 3255 .undefined, 3256 .void, 3257 .null, 3258 .empty_struct, 3259 .@"unreachable", 3260 .generic_poison, 3261 => unreachable, // non-runtime values 3262 .false, .true => return WValue{ .imm32 = switch (simple_value) { 3263 .false => 0, 3264 .true => 1, 3265 else => unreachable, 3266 } }, 3267 }, 3268 .variable, 3269 .extern_func, 3270 .func, 3271 .enum_literal, 3272 .empty_enum_value, 3273 => unreachable, // non-runtime values 3274 .int => { 3275 const int_info = ty.intInfo(mod); 3276 switch (int_info.signedness) { 3277 .signed => switch (int_info.bits) { 3278 0...32 => return WValue{ .imm32 = @as(u32, @intCast(toTwosComplement( 3279 val.toSignedInt(mod), 3280 @as(u6, @intCast(int_info.bits)), 3281 ))) }, 3282 33...64 => return WValue{ .imm64 = toTwosComplement( 3283 val.toSignedInt(mod), 3284 @as(u7, @intCast(int_info.bits)), 3285 ) }, 3286 else => unreachable, 3287 }, 3288 .unsigned => switch (int_info.bits) { 3289 0...32 => return WValue{ .imm32 = @as(u32, @intCast(val.toUnsignedInt(mod))) }, 3290 33...64 => return WValue{ .imm64 = val.toUnsignedInt(mod) }, 3291 else => unreachable, 3292 }, 3293 } 3294 }, 3295 .err => |err| { 3296 const int = try mod.getErrorValue(err.name); 3297 return WValue{ .imm32 = int }; 3298 }, 3299 .error_union => |error_union| { 3300 const err_int_ty = try mod.errorIntType(); 3301 const err_tv: TypedValue = switch (error_union.val) { 3302 .err_name => |err_name| .{ 3303 .ty = ty.errorUnionSet(mod), 3304 .val = Value.fromInterned((try mod.intern(.{ .err = .{ 3305 .ty = ty.errorUnionSet(mod).toIntern(), 3306 .name = err_name, 3307 } }))), 3308 }, 3309 .payload => .{ 3310 .ty = err_int_ty, 3311 .val = try mod.intValue(err_int_ty, 0), 3312 }, 3313 }; 3314 const payload_type = ty.errorUnionPayload(mod); 3315 if (!payload_type.hasRuntimeBitsIgnoreComptime(mod)) { 3316 // We use the error type directly as the type. 3317 return func.lowerConstant(err_tv.val, err_tv.ty); 3318 } 3319 3320 return func.fail("Wasm TODO: lowerConstant error union with non-zero-bit payload type", .{}); 3321 }, 3322 .enum_tag => |enum_tag| { 3323 const int_tag_ty = ip.typeOf(enum_tag.int); 3324 return func.lowerConstant(Value.fromInterned(enum_tag.int), Type.fromInterned(int_tag_ty)); 3325 }, 3326 .float => |float| switch (float.storage) { 3327 .f16 => |f16_val| return WValue{ .imm32 = @as(u16, @bitCast(f16_val)) }, 3328 .f32 => |f32_val| return WValue{ .float32 = f32_val }, 3329 .f64 => |f64_val| return WValue{ .float64 = f64_val }, 3330 else => unreachable, 3331 }, 3332 .ptr => |ptr| switch (ptr.addr) { 3333 .decl => |decl| return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, decl, 0), 3334 .mut_decl => |mut_decl| return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, mut_decl.decl, 0), 3335 .int => |int| return func.lowerConstant(Value.fromInterned(int), Type.fromInterned(ip.typeOf(int))), 3336 .opt_payload, .elem, .field => return func.lowerParentPtr(val, 0), 3337 .anon_decl => |ad| return func.lowerAnonDeclRef(ad, 0), 3338 else => return func.fail("Wasm TODO: lowerConstant for other const addr tag {}", .{ptr.addr}), 3339 }, 3340 .opt => if (ty.optionalReprIsPayload(mod)) { 3341 const pl_ty = ty.optionalChild(mod); 3342 if (val.optionalValue(mod)) |payload| { 3343 return func.lowerConstant(payload, pl_ty); 3344 } else { 3345 return WValue{ .imm32 = 0 }; 3346 } 3347 } else { 3348 return WValue{ .imm32 = @intFromBool(!val.isNull(mod)) }; 3349 }, 3350 .aggregate => switch (ip.indexToKey(ty.ip_index)) { 3351 .array_type => return func.fail("Wasm TODO: LowerConstant for {}", .{ty.fmt(mod)}), 3352 .vector_type => { 3353 assert(determineSimdStoreStrategy(ty, mod) == .direct); 3354 var buf: [16]u8 = undefined; 3355 val.writeToMemory(ty, mod, &buf) catch unreachable; 3356 return func.storeSimdImmd(buf); 3357 }, 3358 .struct_type => |struct_type| { 3359 // non-packed structs are not handled in this function because they 3360 // are by-ref types. 3361 assert(struct_type.layout == .Packed); 3362 var buf: [8]u8 = .{0} ** 8; // zero the buffer so we do not read 0xaa as integer 3363 val.writeToPackedMemory(ty, mod, &buf, 0) catch unreachable; 3364 const backing_int_ty = Type.fromInterned(struct_type.backingIntType(ip).*); 3365 const int_val = try mod.intValue( 3366 backing_int_ty, 3367 mem.readInt(u64, &buf, .little), 3368 ); 3369 return func.lowerConstant(int_val, backing_int_ty); 3370 }, 3371 else => unreachable, 3372 }, 3373 .un => |un| { 3374 // in this case we have a packed union which will not be passed by reference. 3375 const constant_ty = if (un.tag == .none) 3376 try ty.unionBackingType(mod) 3377 else field_ty: { 3378 const union_obj = mod.typeToUnion(ty).?; 3379 const field_index = mod.unionTagFieldIndex(union_obj, Value.fromInterned(un.tag)).?; 3380 break :field_ty Type.fromInterned(union_obj.field_types.get(ip)[field_index]); 3381 }; 3382 return func.lowerConstant(Value.fromInterned(un.val), constant_ty); 3383 }, 3384 .memoized_call => unreachable, 3385 } 3386 } 3387 3388 /// Stores the value as a 128bit-immediate value by storing it inside 3389 /// the list and returning the index into this list as `WValue`. 3390 fn storeSimdImmd(func: *CodeGen, value: [16]u8) !WValue { 3391 const index = @as(u32, @intCast(func.simd_immediates.items.len)); 3392 try func.simd_immediates.append(func.gpa, value); 3393 return WValue{ .imm128 = index }; 3394 } 3395 3396 fn emitUndefined(func: *CodeGen, ty: Type) InnerError!WValue { 3397 const mod = func.bin_file.base.comp.module.?; 3398 const ip = &mod.intern_pool; 3399 switch (ty.zigTypeTag(mod)) { 3400 .Bool, .ErrorSet => return WValue{ .imm32 = 0xaaaaaaaa }, 3401 .Int, .Enum => switch (ty.intInfo(mod).bits) { 3402 0...32 => return WValue{ .imm32 = 0xaaaaaaaa }, 3403 33...64 => return WValue{ .imm64 = 0xaaaaaaaaaaaaaaaa }, 3404 else => unreachable, 3405 }, 3406 .Float => switch (ty.floatBits(func.target)) { 3407 16 => return WValue{ .imm32 = 0xaaaaaaaa }, 3408 32 => return WValue{ .float32 = @as(f32, @bitCast(@as(u32, 0xaaaaaaaa))) }, 3409 64 => return WValue{ .float64 = @as(f64, @bitCast(@as(u64, 0xaaaaaaaaaaaaaaaa))) }, 3410 else => unreachable, 3411 }, 3412 .Pointer => switch (func.arch()) { 3413 .wasm32 => return WValue{ .imm32 = 0xaaaaaaaa }, 3414 .wasm64 => return WValue{ .imm64 = 0xaaaaaaaaaaaaaaaa }, 3415 else => unreachable, 3416 }, 3417 .Optional => { 3418 const pl_ty = ty.optionalChild(mod); 3419 if (ty.optionalReprIsPayload(mod)) { 3420 return func.emitUndefined(pl_ty); 3421 } 3422 return WValue{ .imm32 = 0xaaaaaaaa }; 3423 }, 3424 .ErrorUnion => { 3425 return WValue{ .imm32 = 0xaaaaaaaa }; 3426 }, 3427 .Struct => { 3428 const packed_struct = mod.typeToPackedStruct(ty).?; 3429 return func.emitUndefined(Type.fromInterned(packed_struct.backingIntType(ip).*)); 3430 }, 3431 else => return func.fail("Wasm TODO: emitUndefined for type: {}\n", .{ty.zigTypeTag(mod)}), 3432 } 3433 } 3434 3435 /// Returns a `Value` as a signed 32 bit value. 3436 /// It's illegal to provide a value with a type that cannot be represented 3437 /// as an integer value. 3438 fn valueAsI32(func: *const CodeGen, val: Value, ty: Type) i32 { 3439 const mod = func.bin_file.base.comp.module.?; 3440 3441 switch (val.ip_index) { 3442 .none => {}, 3443 .bool_true => return 1, 3444 .bool_false => return 0, 3445 else => return switch (mod.intern_pool.indexToKey(val.ip_index)) { 3446 .enum_tag => |enum_tag| intIndexAsI32(&mod.intern_pool, enum_tag.int, mod), 3447 .int => |int| intStorageAsI32(int.storage, mod), 3448 .ptr => |ptr| intIndexAsI32(&mod.intern_pool, ptr.addr.int, mod), 3449 .err => |err| @as(i32, @bitCast(@as(Module.ErrorInt, @intCast(mod.global_error_set.getIndex(err.name).?)))), 3450 else => unreachable, 3451 }, 3452 } 3453 3454 return switch (ty.zigTypeTag(mod)) { 3455 .ErrorSet => @as(i32, @bitCast(val.getErrorInt(mod))), 3456 else => unreachable, // Programmer called this function for an illegal type 3457 }; 3458 } 3459 3460 fn intIndexAsI32(ip: *const InternPool, int: InternPool.Index, mod: *Module) i32 { 3461 return intStorageAsI32(ip.indexToKey(int).int.storage, mod); 3462 } 3463 3464 fn intStorageAsI32(storage: InternPool.Key.Int.Storage, mod: *Module) i32 { 3465 return switch (storage) { 3466 .i64 => |x| @as(i32, @intCast(x)), 3467 .u64 => |x| @as(i32, @bitCast(@as(u32, @intCast(x)))), 3468 .big_int => unreachable, 3469 .lazy_align => |ty| @as(i32, @bitCast(@as(u32, @intCast(Type.fromInterned(ty).abiAlignment(mod).toByteUnits(0))))), 3470 .lazy_size => |ty| @as(i32, @bitCast(@as(u32, @intCast(Type.fromInterned(ty).abiSize(mod))))), 3471 }; 3472 } 3473 3474 fn airBlock(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3475 const mod = func.bin_file.base.comp.module.?; 3476 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 3477 const block_ty = ty_pl.ty.toType(); 3478 const wasm_block_ty = genBlockType(block_ty, mod); 3479 const extra = func.air.extraData(Air.Block, ty_pl.payload); 3480 const body: []const Air.Inst.Index = @ptrCast(func.air.extra[extra.end..][0..extra.data.body_len]); 3481 3482 // if wasm_block_ty is non-empty, we create a register to store the temporary value 3483 const block_result: WValue = if (wasm_block_ty != wasm.block_empty) blk: { 3484 const ty: Type = if (isByRef(block_ty, mod)) Type.u32 else block_ty; 3485 break :blk try func.ensureAllocLocal(ty); // make sure it's a clean local as it may never get overwritten 3486 } else WValue.none; 3487 3488 try func.startBlock(.block, wasm.block_empty); 3489 // Here we set the current block idx, so breaks know the depth to jump 3490 // to when breaking out. 3491 try func.blocks.putNoClobber(func.gpa, inst, .{ 3492 .label = func.block_depth, 3493 .value = block_result, 3494 }); 3495 3496 try func.genBody(body); 3497 try func.endBlock(); 3498 3499 const liveness = func.liveness.getBlock(inst); 3500 try func.currentBranch().values.ensureUnusedCapacity(func.gpa, liveness.deaths.len); 3501 3502 func.finishAir(inst, block_result, &.{}); 3503 } 3504 3505 /// appends a new wasm block to the code section and increases the `block_depth` by 1 3506 fn startBlock(func: *CodeGen, block_tag: wasm.Opcode, valtype: u8) !void { 3507 func.block_depth += 1; 3508 try func.addInst(.{ 3509 .tag = Mir.Inst.Tag.fromOpcode(block_tag), 3510 .data = .{ .block_type = valtype }, 3511 }); 3512 } 3513 3514 /// Ends the current wasm block and decreases the `block_depth` by 1 3515 fn endBlock(func: *CodeGen) !void { 3516 try func.addTag(.end); 3517 func.block_depth -= 1; 3518 } 3519 3520 fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3521 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 3522 const loop = func.air.extraData(Air.Block, ty_pl.payload); 3523 const body: []const Air.Inst.Index = @ptrCast(func.air.extra[loop.end..][0..loop.data.body_len]); 3524 3525 // result type of loop is always 'noreturn', meaning we can always 3526 // emit the wasm type 'block_empty'. 3527 try func.startBlock(.loop, wasm.block_empty); 3528 try func.genBody(body); 3529 3530 // breaking to the index of a loop block will continue the loop instead 3531 try func.addLabel(.br, 0); 3532 try func.endBlock(); 3533 3534 func.finishAir(inst, .none, &.{}); 3535 } 3536 3537 fn airCondBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3538 const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 3539 const condition = try func.resolveInst(pl_op.operand); 3540 const extra = func.air.extraData(Air.CondBr, pl_op.payload); 3541 const then_body: []const Air.Inst.Index = @ptrCast(func.air.extra[extra.end..][0..extra.data.then_body_len]); 3542 const else_body: []const Air.Inst.Index = @ptrCast(func.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]); 3543 const liveness_condbr = func.liveness.getCondBr(inst); 3544 3545 // result type is always noreturn, so use `block_empty` as type. 3546 try func.startBlock(.block, wasm.block_empty); 3547 // emit the conditional value 3548 try func.emitWValue(condition); 3549 3550 // we inserted the block in front of the condition 3551 // so now check if condition matches. If not, break outside this block 3552 // and continue with the then codepath 3553 try func.addLabel(.br_if, 0); 3554 3555 try func.branches.ensureUnusedCapacity(func.gpa, 2); 3556 { 3557 func.branches.appendAssumeCapacity(.{}); 3558 try func.currentBranch().values.ensureUnusedCapacity(func.gpa, @as(u32, @intCast(liveness_condbr.else_deaths.len))); 3559 defer { 3560 var else_stack = func.branches.pop(); 3561 else_stack.deinit(func.gpa); 3562 } 3563 try func.genBody(else_body); 3564 try func.endBlock(); 3565 } 3566 3567 // Outer block that matches the condition 3568 { 3569 func.branches.appendAssumeCapacity(.{}); 3570 try func.currentBranch().values.ensureUnusedCapacity(func.gpa, @as(u32, @intCast(liveness_condbr.then_deaths.len))); 3571 defer { 3572 var then_stack = func.branches.pop(); 3573 then_stack.deinit(func.gpa); 3574 } 3575 try func.genBody(then_body); 3576 } 3577 3578 func.finishAir(inst, .none, &.{}); 3579 } 3580 3581 fn airCmp(func: *CodeGen, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!void { 3582 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 3583 3584 const lhs = try func.resolveInst(bin_op.lhs); 3585 const rhs = try func.resolveInst(bin_op.rhs); 3586 const operand_ty = func.typeOf(bin_op.lhs); 3587 const result = try (try func.cmp(lhs, rhs, operand_ty, op)).toLocal(func, Type.u32); // comparison result is always 32 bits 3588 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 3589 } 3590 3591 /// Compares two operands. 3592 /// Asserts rhs is not a stack value when the lhs isn't a stack value either 3593 /// NOTE: This leaves the result on top of the stack, rather than a new local. 3594 fn cmp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: std.math.CompareOperator) InnerError!WValue { 3595 assert(!(lhs != .stack and rhs == .stack)); 3596 const mod = func.bin_file.base.comp.module.?; 3597 if (ty.zigTypeTag(mod) == .Optional and !ty.optionalReprIsPayload(mod)) { 3598 const payload_ty = ty.optionalChild(mod); 3599 if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) { 3600 // When we hit this case, we must check the value of optionals 3601 // that are not pointers. This means first checking against non-null for 3602 // both lhs and rhs, as well as checking the payload are matching of lhs and rhs 3603 return func.cmpOptionals(lhs, rhs, ty, op); 3604 } 3605 } else if (ty.isAnyFloat()) { 3606 return func.cmpFloat(ty, lhs, rhs, op); 3607 } else if (isByRef(ty, mod)) { 3608 return func.cmpBigInt(lhs, rhs, ty, op); 3609 } 3610 3611 const signedness: std.builtin.Signedness = blk: { 3612 // by default we tell the operand type is unsigned (i.e. bools and enum values) 3613 if (ty.zigTypeTag(mod) != .Int) break :blk .unsigned; 3614 3615 // incase of an actual integer, we emit the correct signedness 3616 break :blk ty.intInfo(mod).signedness; 3617 }; 3618 const extend_sign = blk: { 3619 // do we need to extend the sign bit? 3620 if (signedness != .signed) break :blk false; 3621 if (op == .eq or op == .neq) break :blk false; 3622 const int_bits = ty.intInfo(mod).bits; 3623 const wasm_bits = toWasmBits(int_bits) orelse unreachable; 3624 break :blk (wasm_bits != int_bits); 3625 }; 3626 3627 const lhs_wasm = if (extend_sign) 3628 try func.signExtendInt(lhs, ty) 3629 else 3630 lhs; 3631 3632 const rhs_wasm = if (extend_sign) 3633 try func.signExtendInt(rhs, ty) 3634 else 3635 rhs; 3636 3637 // ensure that when we compare pointers, we emit 3638 // the true pointer of a stack value, rather than the stack pointer. 3639 try func.lowerToStack(lhs_wasm); 3640 try func.lowerToStack(rhs_wasm); 3641 3642 const opcode: wasm.Opcode = buildOpcode(.{ 3643 .valtype1 = typeToValtype(ty, mod), 3644 .op = switch (op) { 3645 .lt => .lt, 3646 .lte => .le, 3647 .eq => .eq, 3648 .neq => .ne, 3649 .gte => .ge, 3650 .gt => .gt, 3651 }, 3652 .signedness = signedness, 3653 }); 3654 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); 3655 3656 return WValue{ .stack = {} }; 3657 } 3658 3659 /// Compares two floats. 3660 /// NOTE: Leaves the result of the comparison on top of the stack. 3661 fn cmpFloat(func: *CodeGen, ty: Type, lhs: WValue, rhs: WValue, cmp_op: std.math.CompareOperator) InnerError!WValue { 3662 const float_bits = ty.floatBits(func.target); 3663 3664 const op: Op = switch (cmp_op) { 3665 .lt => .lt, 3666 .lte => .le, 3667 .eq => .eq, 3668 .neq => .ne, 3669 .gte => .ge, 3670 .gt => .gt, 3671 }; 3672 3673 switch (float_bits) { 3674 16 => { 3675 _ = try func.fpext(lhs, Type.f16, Type.f32); 3676 _ = try func.fpext(rhs, Type.f16, Type.f32); 3677 const opcode = buildOpcode(.{ .op = op, .valtype1 = .f32 }); 3678 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); 3679 return .stack; 3680 }, 3681 32, 64 => { 3682 try func.emitWValue(lhs); 3683 try func.emitWValue(rhs); 3684 const val_type: wasm.Valtype = if (float_bits == 32) .f32 else .f64; 3685 const opcode = buildOpcode(.{ .op = op, .valtype1 = val_type }); 3686 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); 3687 return .stack; 3688 }, 3689 80, 128 => { 3690 var fn_name_buf: [32]u8 = undefined; 3691 const fn_name = std.fmt.bufPrint(&fn_name_buf, "__{s}{s}f2", .{ 3692 @tagName(op), target_util.compilerRtFloatAbbrev(float_bits), 3693 }) catch unreachable; 3694 3695 const result = try func.callIntrinsic(fn_name, &.{ ty.ip_index, ty.ip_index }, Type.bool, &.{ lhs, rhs }); 3696 return func.cmp(result, WValue{ .imm32 = 0 }, Type.i32, cmp_op); 3697 }, 3698 else => unreachable, 3699 } 3700 } 3701 3702 fn airCmpVector(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3703 _ = inst; 3704 return func.fail("TODO implement airCmpVector for wasm", .{}); 3705 } 3706 3707 fn airCmpLtErrorsLen(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3708 const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 3709 const operand = try func.resolveInst(un_op); 3710 const sym_index = try func.bin_file.getGlobalSymbol("__zig_errors_len", null); 3711 const errors_len = WValue{ .memory = sym_index }; 3712 3713 try func.emitWValue(operand); 3714 const mod = func.bin_file.base.comp.module.?; 3715 const err_int_ty = try mod.errorIntType(); 3716 const errors_len_val = try func.load(errors_len, err_int_ty, 0); 3717 const result = try func.cmp(.stack, errors_len_val, err_int_ty, .lt); 3718 3719 return func.finishAir(inst, try result.toLocal(func, Type.bool), &.{un_op}); 3720 } 3721 3722 fn airBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3723 const mod = func.bin_file.base.comp.module.?; 3724 const br = func.air.instructions.items(.data)[@intFromEnum(inst)].br; 3725 const block = func.blocks.get(br.block_inst).?; 3726 3727 // if operand has codegen bits we should break with a value 3728 if (func.typeOf(br.operand).hasRuntimeBitsIgnoreComptime(mod)) { 3729 const operand = try func.resolveInst(br.operand); 3730 try func.lowerToStack(operand); 3731 3732 if (block.value != .none) { 3733 try func.addLabel(.local_set, block.value.local.value); 3734 } 3735 } 3736 3737 // We map every block to its block index. 3738 // We then determine how far we have to jump to it by subtracting it from current block depth 3739 const idx: u32 = func.block_depth - block.label; 3740 try func.addLabel(.br, idx); 3741 3742 func.finishAir(inst, .none, &.{br.operand}); 3743 } 3744 3745 fn airNot(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3746 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 3747 3748 const operand = try func.resolveInst(ty_op.operand); 3749 const operand_ty = func.typeOf(ty_op.operand); 3750 const mod = func.bin_file.base.comp.module.?; 3751 3752 const result = result: { 3753 if (operand_ty.zigTypeTag(mod) == .Bool) { 3754 try func.emitWValue(operand); 3755 try func.addTag(.i32_eqz); 3756 const not_tmp = try func.allocLocal(operand_ty); 3757 try func.addLabel(.local_set, not_tmp.local.value); 3758 break :result not_tmp; 3759 } else { 3760 const operand_bits = operand_ty.intInfo(mod).bits; 3761 const wasm_bits = toWasmBits(operand_bits) orelse { 3762 return func.fail("TODO: Implement binary NOT for integer with bitsize '{d}'", .{operand_bits}); 3763 }; 3764 3765 switch (wasm_bits) { 3766 32 => { 3767 const bin_op = try func.binOp(operand, .{ .imm32 = ~@as(u32, 0) }, operand_ty, .xor); 3768 break :result try (try func.wrapOperand(bin_op, operand_ty)).toLocal(func, operand_ty); 3769 }, 3770 64 => { 3771 const bin_op = try func.binOp(operand, .{ .imm64 = ~@as(u64, 0) }, operand_ty, .xor); 3772 break :result try (try func.wrapOperand(bin_op, operand_ty)).toLocal(func, operand_ty); 3773 }, 3774 128 => { 3775 const result_ptr = try func.allocStack(operand_ty); 3776 try func.emitWValue(result_ptr); 3777 const msb = try func.load(operand, Type.u64, 0); 3778 const msb_xor = try func.binOp(msb, .{ .imm64 = ~@as(u64, 0) }, Type.u64, .xor); 3779 try func.store(.{ .stack = {} }, msb_xor, Type.u64, 0 + result_ptr.offset()); 3780 3781 try func.emitWValue(result_ptr); 3782 const lsb = try func.load(operand, Type.u64, 8); 3783 const lsb_xor = try func.binOp(lsb, .{ .imm64 = ~@as(u64, 0) }, Type.u64, .xor); 3784 try func.store(result_ptr, lsb_xor, Type.u64, 8 + result_ptr.offset()); 3785 break :result result_ptr; 3786 }, 3787 else => unreachable, 3788 } 3789 } 3790 }; 3791 func.finishAir(inst, result, &.{ty_op.operand}); 3792 } 3793 3794 fn airTrap(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3795 try func.addTag(.@"unreachable"); 3796 func.finishAir(inst, .none, &.{}); 3797 } 3798 3799 fn airBreakpoint(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3800 // unsupported by wasm itfunc. Can be implemented once we support DWARF 3801 // for wasm 3802 try func.addTag(.@"unreachable"); 3803 func.finishAir(inst, .none, &.{}); 3804 } 3805 3806 fn airUnreachable(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3807 try func.addTag(.@"unreachable"); 3808 func.finishAir(inst, .none, &.{}); 3809 } 3810 3811 fn airBitcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3812 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 3813 const result = result: { 3814 const operand = try func.resolveInst(ty_op.operand); 3815 const wanted_ty = func.typeOfIndex(inst); 3816 const given_ty = func.typeOf(ty_op.operand); 3817 if (given_ty.isAnyFloat() or wanted_ty.isAnyFloat()) { 3818 const bitcast_result = try func.bitcast(wanted_ty, given_ty, operand); 3819 break :result try bitcast_result.toLocal(func, wanted_ty); 3820 } 3821 const mod = func.bin_file.base.comp.module.?; 3822 if (isByRef(given_ty, mod) and !isByRef(wanted_ty, mod)) { 3823 const loaded_memory = try func.load(operand, wanted_ty, 0); 3824 break :result try loaded_memory.toLocal(func, wanted_ty); 3825 } 3826 if (!isByRef(given_ty, mod) and isByRef(wanted_ty, mod)) { 3827 const stack_memory = try func.allocStack(wanted_ty); 3828 try func.store(stack_memory, operand, given_ty, 0); 3829 break :result stack_memory; 3830 } 3831 break :result func.reuseOperand(ty_op.operand, operand); 3832 }; 3833 func.finishAir(inst, result, &.{ty_op.operand}); 3834 } 3835 3836 fn bitcast(func: *CodeGen, wanted_ty: Type, given_ty: Type, operand: WValue) InnerError!WValue { 3837 const mod = func.bin_file.base.comp.module.?; 3838 // if we bitcast a float to or from an integer we must use the 'reinterpret' instruction 3839 if (!(wanted_ty.isAnyFloat() or given_ty.isAnyFloat())) return operand; 3840 if (wanted_ty.ip_index == .f16_type or given_ty.ip_index == .f16_type) return operand; 3841 if (wanted_ty.bitSize(mod) > 64) return operand; 3842 assert((wanted_ty.isInt(mod) and given_ty.isAnyFloat()) or (wanted_ty.isAnyFloat() and given_ty.isInt(mod))); 3843 3844 const opcode = buildOpcode(.{ 3845 .op = .reinterpret, 3846 .valtype1 = typeToValtype(wanted_ty, mod), 3847 .valtype2 = typeToValtype(given_ty, mod), 3848 }); 3849 try func.emitWValue(operand); 3850 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); 3851 return WValue{ .stack = {} }; 3852 } 3853 3854 fn airStructFieldPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3855 const mod = func.bin_file.base.comp.module.?; 3856 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 3857 const extra = func.air.extraData(Air.StructField, ty_pl.payload); 3858 3859 const struct_ptr = try func.resolveInst(extra.data.struct_operand); 3860 const struct_ptr_ty = func.typeOf(extra.data.struct_operand); 3861 const struct_ty = struct_ptr_ty.childType(mod); 3862 const result = try func.structFieldPtr(inst, extra.data.struct_operand, struct_ptr, struct_ptr_ty, struct_ty, extra.data.field_index); 3863 func.finishAir(inst, result, &.{extra.data.struct_operand}); 3864 } 3865 3866 fn airStructFieldPtrIndex(func: *CodeGen, inst: Air.Inst.Index, index: u32) InnerError!void { 3867 const mod = func.bin_file.base.comp.module.?; 3868 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 3869 const struct_ptr = try func.resolveInst(ty_op.operand); 3870 const struct_ptr_ty = func.typeOf(ty_op.operand); 3871 const struct_ty = struct_ptr_ty.childType(mod); 3872 3873 const result = try func.structFieldPtr(inst, ty_op.operand, struct_ptr, struct_ptr_ty, struct_ty, index); 3874 func.finishAir(inst, result, &.{ty_op.operand}); 3875 } 3876 3877 fn structFieldPtr( 3878 func: *CodeGen, 3879 inst: Air.Inst.Index, 3880 ref: Air.Inst.Ref, 3881 struct_ptr: WValue, 3882 struct_ptr_ty: Type, 3883 struct_ty: Type, 3884 index: u32, 3885 ) InnerError!WValue { 3886 const mod = func.bin_file.base.comp.module.?; 3887 const result_ty = func.typeOfIndex(inst); 3888 const struct_ptr_ty_info = struct_ptr_ty.ptrInfo(mod); 3889 3890 const offset = switch (struct_ty.containerLayout(mod)) { 3891 .Packed => switch (struct_ty.zigTypeTag(mod)) { 3892 .Struct => offset: { 3893 if (result_ty.ptrInfo(mod).packed_offset.host_size != 0) { 3894 break :offset @as(u32, 0); 3895 } 3896 const struct_type = mod.typeToStruct(struct_ty).?; 3897 break :offset @divExact(mod.structPackedFieldBitOffset(struct_type, index) + struct_ptr_ty_info.packed_offset.bit_offset, 8); 3898 }, 3899 .Union => 0, 3900 else => unreachable, 3901 }, 3902 else => struct_ty.structFieldOffset(index, mod), 3903 }; 3904 // save a load and store when we can simply reuse the operand 3905 if (offset == 0) { 3906 return func.reuseOperand(ref, struct_ptr); 3907 } 3908 switch (struct_ptr) { 3909 .stack_offset => |stack_offset| { 3910 return WValue{ .stack_offset = .{ .value = stack_offset.value + @as(u32, @intCast(offset)), .references = 1 } }; 3911 }, 3912 else => return func.buildPointerOffset(struct_ptr, offset, .new), 3913 } 3914 } 3915 3916 fn airStructFieldVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 3917 const mod = func.bin_file.base.comp.module.?; 3918 const ip = &mod.intern_pool; 3919 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 3920 const struct_field = func.air.extraData(Air.StructField, ty_pl.payload).data; 3921 3922 const struct_ty = func.typeOf(struct_field.struct_operand); 3923 const operand = try func.resolveInst(struct_field.struct_operand); 3924 const field_index = struct_field.field_index; 3925 const field_ty = struct_ty.structFieldType(field_index, mod); 3926 if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) return func.finishAir(inst, .none, &.{struct_field.struct_operand}); 3927 3928 const result = switch (struct_ty.containerLayout(mod)) { 3929 .Packed => switch (struct_ty.zigTypeTag(mod)) { 3930 .Struct => result: { 3931 const packed_struct = mod.typeToPackedStruct(struct_ty).?; 3932 const offset = mod.structPackedFieldBitOffset(packed_struct, field_index); 3933 const backing_ty = Type.fromInterned(packed_struct.backingIntType(ip).*); 3934 const wasm_bits = toWasmBits(backing_ty.intInfo(mod).bits) orelse { 3935 return func.fail("TODO: airStructFieldVal for packed structs larger than 128 bits", .{}); 3936 }; 3937 const const_wvalue = if (wasm_bits == 32) 3938 WValue{ .imm32 = offset } 3939 else if (wasm_bits == 64) 3940 WValue{ .imm64 = offset } 3941 else 3942 return func.fail("TODO: airStructFieldVal for packed structs larger than 64 bits", .{}); 3943 3944 // for first field we don't require any shifting 3945 const shifted_value = if (offset == 0) 3946 operand 3947 else 3948 try func.binOp(operand, const_wvalue, backing_ty, .shr); 3949 3950 if (field_ty.zigTypeTag(mod) == .Float) { 3951 const int_type = try mod.intType(.unsigned, @as(u16, @intCast(field_ty.bitSize(mod)))); 3952 const truncated = try func.trunc(shifted_value, int_type, backing_ty); 3953 const bitcasted = try func.bitcast(field_ty, int_type, truncated); 3954 break :result try bitcasted.toLocal(func, field_ty); 3955 } else if (field_ty.isPtrAtRuntime(mod) and packed_struct.field_types.len == 1) { 3956 // In this case we do not have to perform any transformations, 3957 // we can simply reuse the operand. 3958 break :result func.reuseOperand(struct_field.struct_operand, operand); 3959 } else if (field_ty.isPtrAtRuntime(mod)) { 3960 const int_type = try mod.intType(.unsigned, @as(u16, @intCast(field_ty.bitSize(mod)))); 3961 const truncated = try func.trunc(shifted_value, int_type, backing_ty); 3962 break :result try truncated.toLocal(func, field_ty); 3963 } 3964 const truncated = try func.trunc(shifted_value, field_ty, backing_ty); 3965 break :result try truncated.toLocal(func, field_ty); 3966 }, 3967 .Union => result: { 3968 if (isByRef(struct_ty, mod)) { 3969 if (!isByRef(field_ty, mod)) { 3970 const val = try func.load(operand, field_ty, 0); 3971 break :result try val.toLocal(func, field_ty); 3972 } else { 3973 const new_stack_val = try func.allocStack(field_ty); 3974 try func.store(new_stack_val, operand, field_ty, 0); 3975 break :result new_stack_val; 3976 } 3977 } 3978 3979 const union_int_type = try mod.intType(.unsigned, @as(u16, @intCast(struct_ty.bitSize(mod)))); 3980 if (field_ty.zigTypeTag(mod) == .Float) { 3981 const int_type = try mod.intType(.unsigned, @as(u16, @intCast(field_ty.bitSize(mod)))); 3982 const truncated = try func.trunc(operand, int_type, union_int_type); 3983 const bitcasted = try func.bitcast(field_ty, int_type, truncated); 3984 break :result try bitcasted.toLocal(func, field_ty); 3985 } else if (field_ty.isPtrAtRuntime(mod)) { 3986 const int_type = try mod.intType(.unsigned, @as(u16, @intCast(field_ty.bitSize(mod)))); 3987 const truncated = try func.trunc(operand, int_type, union_int_type); 3988 break :result try truncated.toLocal(func, field_ty); 3989 } 3990 const truncated = try func.trunc(operand, field_ty, union_int_type); 3991 break :result try truncated.toLocal(func, field_ty); 3992 }, 3993 else => unreachable, 3994 }, 3995 else => result: { 3996 const offset = std.math.cast(u32, struct_ty.structFieldOffset(field_index, mod)) orelse { 3997 return func.fail("Field type '{}' too big to fit into stack frame", .{field_ty.fmt(mod)}); 3998 }; 3999 if (isByRef(field_ty, mod)) { 4000 switch (operand) { 4001 .stack_offset => |stack_offset| { 4002 break :result WValue{ .stack_offset = .{ .value = stack_offset.value + offset, .references = 1 } }; 4003 }, 4004 else => break :result try func.buildPointerOffset(operand, offset, .new), 4005 } 4006 } 4007 const field = try func.load(operand, field_ty, offset); 4008 break :result try field.toLocal(func, field_ty); 4009 }, 4010 }; 4011 4012 func.finishAir(inst, result, &.{struct_field.struct_operand}); 4013 } 4014 4015 fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4016 const mod = func.bin_file.base.comp.module.?; 4017 // result type is always 'noreturn' 4018 const blocktype = wasm.block_empty; 4019 const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 4020 const target = try func.resolveInst(pl_op.operand); 4021 const target_ty = func.typeOf(pl_op.operand); 4022 const switch_br = func.air.extraData(Air.SwitchBr, pl_op.payload); 4023 const liveness = try func.liveness.getSwitchBr(func.gpa, inst, switch_br.data.cases_len + 1); 4024 defer func.gpa.free(liveness.deaths); 4025 4026 var extra_index: usize = switch_br.end; 4027 var case_i: u32 = 0; 4028 4029 // a list that maps each value with its value and body based on the order inside the list. 4030 const CaseValue = struct { integer: i32, value: Value }; 4031 var case_list = try std.ArrayList(struct { 4032 values: []const CaseValue, 4033 body: []const Air.Inst.Index, 4034 }).initCapacity(func.gpa, switch_br.data.cases_len); 4035 defer for (case_list.items) |case| { 4036 func.gpa.free(case.values); 4037 } else case_list.deinit(); 4038 4039 var lowest_maybe: ?i32 = null; 4040 var highest_maybe: ?i32 = null; 4041 while (case_i < switch_br.data.cases_len) : (case_i += 1) { 4042 const case = func.air.extraData(Air.SwitchBr.Case, extra_index); 4043 const items: []const Air.Inst.Ref = @ptrCast(func.air.extra[case.end..][0..case.data.items_len]); 4044 const case_body: []const Air.Inst.Index = @ptrCast(func.air.extra[case.end + items.len ..][0..case.data.body_len]); 4045 extra_index = case.end + items.len + case_body.len; 4046 const values = try func.gpa.alloc(CaseValue, items.len); 4047 errdefer func.gpa.free(values); 4048 4049 for (items, 0..) |ref, i| { 4050 const item_val = (try func.air.value(ref, mod)).?; 4051 const int_val = func.valueAsI32(item_val, target_ty); 4052 if (lowest_maybe == null or int_val < lowest_maybe.?) { 4053 lowest_maybe = int_val; 4054 } 4055 if (highest_maybe == null or int_val > highest_maybe.?) { 4056 highest_maybe = int_val; 4057 } 4058 values[i] = .{ .integer = int_val, .value = item_val }; 4059 } 4060 4061 case_list.appendAssumeCapacity(.{ .values = values, .body = case_body }); 4062 try func.startBlock(.block, blocktype); 4063 } 4064 4065 // When highest and lowest are null, we have no cases and can use a jump table 4066 const lowest = lowest_maybe orelse 0; 4067 const highest = highest_maybe orelse 0; 4068 // When the highest and lowest values are seperated by '50', 4069 // we define it as sparse and use an if/else-chain, rather than a jump table. 4070 // When the target is an integer size larger than u32, we have no way to use the value 4071 // as an index, therefore we also use an if/else-chain for those cases. 4072 // TODO: Benchmark this to find a proper value, LLVM seems to draw the line at '40~45'. 4073 const is_sparse = highest - lowest > 50 or target_ty.bitSize(mod) > 32; 4074 4075 const else_body: []const Air.Inst.Index = @ptrCast(func.air.extra[extra_index..][0..switch_br.data.else_body_len]); 4076 const has_else_body = else_body.len != 0; 4077 if (has_else_body) { 4078 try func.startBlock(.block, blocktype); 4079 } 4080 4081 if (!is_sparse) { 4082 // Generate the jump table 'br_table' when the prongs are not sparse. 4083 // The value 'target' represents the index into the table. 4084 // Each index in the table represents a label to the branch 4085 // to jump to. 4086 try func.startBlock(.block, blocktype); 4087 try func.emitWValue(target); 4088 if (lowest < 0) { 4089 // since br_table works using indexes, starting from '0', we must ensure all values 4090 // we put inside, are atleast 0. 4091 try func.addImm32(lowest * -1); 4092 try func.addTag(.i32_add); 4093 } else if (lowest > 0) { 4094 // make the index start from 0 by substracting the lowest value 4095 try func.addImm32(lowest); 4096 try func.addTag(.i32_sub); 4097 } 4098 4099 // Account for default branch so always add '1' 4100 const depth = @as(u32, @intCast(highest - lowest + @intFromBool(has_else_body))) + 1; 4101 const jump_table: Mir.JumpTable = .{ .length = depth }; 4102 const table_extra_index = try func.addExtra(jump_table); 4103 try func.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } }); 4104 try func.mir_extra.ensureUnusedCapacity(func.gpa, depth); 4105 var value = lowest; 4106 while (value <= highest) : (value += 1) { 4107 // idx represents the branch we jump to 4108 const idx = blk: { 4109 for (case_list.items, 0..) |case, idx| { 4110 for (case.values) |case_value| { 4111 if (case_value.integer == value) break :blk @as(u32, @intCast(idx)); 4112 } 4113 } 4114 // error sets are almost always sparse so we use the default case 4115 // for errors that are not present in any branch. This is fine as this default 4116 // case will never be hit for those cases but we do save runtime cost and size 4117 // by using a jump table for this instead of if-else chains. 4118 break :blk if (has_else_body or target_ty.zigTypeTag(mod) == .ErrorSet) case_i else unreachable; 4119 }; 4120 func.mir_extra.appendAssumeCapacity(idx); 4121 } else if (has_else_body) { 4122 func.mir_extra.appendAssumeCapacity(case_i); // default branch 4123 } 4124 try func.endBlock(); 4125 } 4126 4127 const signedness: std.builtin.Signedness = blk: { 4128 // by default we tell the operand type is unsigned (i.e. bools and enum values) 4129 if (target_ty.zigTypeTag(mod) != .Int) break :blk .unsigned; 4130 4131 // incase of an actual integer, we emit the correct signedness 4132 break :blk target_ty.intInfo(mod).signedness; 4133 }; 4134 4135 try func.branches.ensureUnusedCapacity(func.gpa, case_list.items.len + @intFromBool(has_else_body)); 4136 for (case_list.items, 0..) |case, index| { 4137 // when sparse, we use if/else-chain, so emit conditional checks 4138 if (is_sparse) { 4139 // for single value prong we can emit a simple if 4140 if (case.values.len == 1) { 4141 try func.emitWValue(target); 4142 const val = try func.lowerConstant(case.values[0].value, target_ty); 4143 try func.emitWValue(val); 4144 const opcode = buildOpcode(.{ 4145 .valtype1 = typeToValtype(target_ty, mod), 4146 .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition. 4147 .signedness = signedness, 4148 }); 4149 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); 4150 try func.addLabel(.br_if, 0); 4151 } else { 4152 // in multi-value prongs we must check if any prongs match the target value. 4153 try func.startBlock(.block, blocktype); 4154 for (case.values) |value| { 4155 try func.emitWValue(target); 4156 const val = try func.lowerConstant(value.value, target_ty); 4157 try func.emitWValue(val); 4158 const opcode = buildOpcode(.{ 4159 .valtype1 = typeToValtype(target_ty, mod), 4160 .op = .eq, 4161 .signedness = signedness, 4162 }); 4163 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); 4164 try func.addLabel(.br_if, 0); 4165 } 4166 // value did not match any of the prong values 4167 try func.addLabel(.br, 1); 4168 try func.endBlock(); 4169 } 4170 } 4171 func.branches.appendAssumeCapacity(.{}); 4172 try func.currentBranch().values.ensureUnusedCapacity(func.gpa, liveness.deaths[index].len); 4173 defer { 4174 var case_branch = func.branches.pop(); 4175 case_branch.deinit(func.gpa); 4176 } 4177 try func.genBody(case.body); 4178 try func.endBlock(); 4179 } 4180 4181 if (has_else_body) { 4182 func.branches.appendAssumeCapacity(.{}); 4183 const else_deaths = liveness.deaths.len - 1; 4184 try func.currentBranch().values.ensureUnusedCapacity(func.gpa, liveness.deaths[else_deaths].len); 4185 defer { 4186 var else_branch = func.branches.pop(); 4187 else_branch.deinit(func.gpa); 4188 } 4189 try func.genBody(else_body); 4190 try func.endBlock(); 4191 } 4192 func.finishAir(inst, .none, &.{}); 4193 } 4194 4195 fn airIsErr(func: *CodeGen, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!void { 4196 const mod = func.bin_file.base.comp.module.?; 4197 const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 4198 const operand = try func.resolveInst(un_op); 4199 const err_union_ty = func.typeOf(un_op); 4200 const pl_ty = err_union_ty.errorUnionPayload(mod); 4201 4202 const result = result: { 4203 if (err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) { 4204 switch (opcode) { 4205 .i32_ne => break :result WValue{ .imm32 = 0 }, 4206 .i32_eq => break :result WValue{ .imm32 = 1 }, 4207 else => unreachable, 4208 } 4209 } 4210 4211 try func.emitWValue(operand); 4212 if (pl_ty.hasRuntimeBitsIgnoreComptime(mod)) { 4213 try func.addMemArg(.i32_load16_u, .{ 4214 .offset = operand.offset() + @as(u32, @intCast(errUnionErrorOffset(pl_ty, mod))), 4215 .alignment = @intCast(Type.anyerror.abiAlignment(mod).toByteUnitsOptional().?), 4216 }); 4217 } 4218 4219 // Compare the error value with '0' 4220 try func.addImm32(0); 4221 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); 4222 4223 const is_err_tmp = try func.allocLocal(Type.i32); 4224 try func.addLabel(.local_set, is_err_tmp.local.value); 4225 break :result is_err_tmp; 4226 }; 4227 func.finishAir(inst, result, &.{un_op}); 4228 } 4229 4230 fn airUnwrapErrUnionPayload(func: *CodeGen, inst: Air.Inst.Index, op_is_ptr: bool) InnerError!void { 4231 const mod = func.bin_file.base.comp.module.?; 4232 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4233 4234 const operand = try func.resolveInst(ty_op.operand); 4235 const op_ty = func.typeOf(ty_op.operand); 4236 const err_ty = if (op_is_ptr) op_ty.childType(mod) else op_ty; 4237 const payload_ty = err_ty.errorUnionPayload(mod); 4238 4239 const result = result: { 4240 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) { 4241 if (op_is_ptr) { 4242 break :result func.reuseOperand(ty_op.operand, operand); 4243 } 4244 break :result WValue{ .none = {} }; 4245 } 4246 4247 const pl_offset = @as(u32, @intCast(errUnionPayloadOffset(payload_ty, mod))); 4248 if (op_is_ptr or isByRef(payload_ty, mod)) { 4249 break :result try func.buildPointerOffset(operand, pl_offset, .new); 4250 } 4251 4252 const payload = try func.load(operand, payload_ty, pl_offset); 4253 break :result try payload.toLocal(func, payload_ty); 4254 }; 4255 func.finishAir(inst, result, &.{ty_op.operand}); 4256 } 4257 4258 fn airUnwrapErrUnionError(func: *CodeGen, inst: Air.Inst.Index, op_is_ptr: bool) InnerError!void { 4259 const mod = func.bin_file.base.comp.module.?; 4260 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4261 4262 const operand = try func.resolveInst(ty_op.operand); 4263 const op_ty = func.typeOf(ty_op.operand); 4264 const err_ty = if (op_is_ptr) op_ty.childType(mod) else op_ty; 4265 const payload_ty = err_ty.errorUnionPayload(mod); 4266 4267 const result = result: { 4268 if (err_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) { 4269 break :result WValue{ .imm32 = 0 }; 4270 } 4271 4272 if (op_is_ptr or !payload_ty.hasRuntimeBitsIgnoreComptime(mod)) { 4273 break :result func.reuseOperand(ty_op.operand, operand); 4274 } 4275 4276 const error_val = try func.load(operand, Type.anyerror, @as(u32, @intCast(errUnionErrorOffset(payload_ty, mod)))); 4277 break :result try error_val.toLocal(func, Type.anyerror); 4278 }; 4279 func.finishAir(inst, result, &.{ty_op.operand}); 4280 } 4281 4282 fn airWrapErrUnionPayload(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4283 const mod = func.bin_file.base.comp.module.?; 4284 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4285 4286 const operand = try func.resolveInst(ty_op.operand); 4287 const err_ty = func.typeOfIndex(inst); 4288 4289 const pl_ty = func.typeOf(ty_op.operand); 4290 const result = result: { 4291 if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) { 4292 break :result func.reuseOperand(ty_op.operand, operand); 4293 } 4294 4295 const err_union = try func.allocStack(err_ty); 4296 const payload_ptr = try func.buildPointerOffset(err_union, @as(u32, @intCast(errUnionPayloadOffset(pl_ty, mod))), .new); 4297 try func.store(payload_ptr, operand, pl_ty, 0); 4298 4299 // ensure we also write '0' to the error part, so any present stack value gets overwritten by it. 4300 try func.emitWValue(err_union); 4301 try func.addImm32(0); 4302 const err_val_offset = @as(u32, @intCast(errUnionErrorOffset(pl_ty, mod))); 4303 try func.addMemArg(.i32_store16, .{ 4304 .offset = err_union.offset() + err_val_offset, 4305 .alignment = 2, 4306 }); 4307 break :result err_union; 4308 }; 4309 func.finishAir(inst, result, &.{ty_op.operand}); 4310 } 4311 4312 fn airWrapErrUnionErr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4313 const mod = func.bin_file.base.comp.module.?; 4314 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4315 4316 const operand = try func.resolveInst(ty_op.operand); 4317 const err_ty = ty_op.ty.toType(); 4318 const pl_ty = err_ty.errorUnionPayload(mod); 4319 4320 const result = result: { 4321 if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) { 4322 break :result func.reuseOperand(ty_op.operand, operand); 4323 } 4324 4325 const err_union = try func.allocStack(err_ty); 4326 // store error value 4327 try func.store(err_union, operand, Type.anyerror, @as(u32, @intCast(errUnionErrorOffset(pl_ty, mod)))); 4328 4329 // write 'undefined' to the payload 4330 const payload_ptr = try func.buildPointerOffset(err_union, @as(u32, @intCast(errUnionPayloadOffset(pl_ty, mod))), .new); 4331 const len = @as(u32, @intCast(err_ty.errorUnionPayload(mod).abiSize(mod))); 4332 try func.memset(Type.u8, payload_ptr, .{ .imm32 = len }, .{ .imm32 = 0xaa }); 4333 4334 break :result err_union; 4335 }; 4336 func.finishAir(inst, result, &.{ty_op.operand}); 4337 } 4338 4339 fn airIntcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4340 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4341 4342 const ty = ty_op.ty.toType(); 4343 const operand = try func.resolveInst(ty_op.operand); 4344 const operand_ty = func.typeOf(ty_op.operand); 4345 const mod = func.bin_file.base.comp.module.?; 4346 if (ty.zigTypeTag(mod) == .Vector or operand_ty.zigTypeTag(mod) == .Vector) { 4347 return func.fail("todo Wasm intcast for vectors", .{}); 4348 } 4349 if (ty.abiSize(mod) > 16 or operand_ty.abiSize(mod) > 16) { 4350 return func.fail("todo Wasm intcast for bitsize > 128", .{}); 4351 } 4352 4353 const op_bits = toWasmBits(@as(u16, @intCast(operand_ty.bitSize(mod)))).?; 4354 const wanted_bits = toWasmBits(@as(u16, @intCast(ty.bitSize(mod)))).?; 4355 const result = if (op_bits == wanted_bits and !ty.isSignedInt(mod)) 4356 func.reuseOperand(ty_op.operand, operand) 4357 else 4358 try (try func.intcast(operand, operand_ty, ty)).toLocal(func, ty); 4359 4360 func.finishAir(inst, result, &.{}); 4361 } 4362 4363 /// Upcasts or downcasts an integer based on the given and wanted types, 4364 /// and stores the result in a new operand. 4365 /// Asserts type's bitsize <= 128 4366 /// NOTE: May leave the result on the top of the stack. 4367 fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError!WValue { 4368 const mod = func.bin_file.base.comp.module.?; 4369 const given_bitsize = @as(u16, @intCast(given.bitSize(mod))); 4370 const wanted_bitsize = @as(u16, @intCast(wanted.bitSize(mod))); 4371 assert(given_bitsize <= 128); 4372 assert(wanted_bitsize <= 128); 4373 4374 const op_bits = toWasmBits(given_bitsize).?; 4375 const wanted_bits = toWasmBits(wanted_bitsize).?; 4376 if (op_bits == wanted_bits) { 4377 if (given.isSignedInt(mod)) { 4378 if (given_bitsize < wanted_bitsize) { 4379 // signed integers are stored as two's complement, 4380 // when we upcast from a smaller integer to larger 4381 // integers, we must get its absolute value similar to 4382 // i64_extend_i32_s instruction. 4383 return func.signExtendInt(operand, given); 4384 } 4385 return func.wrapOperand(operand, wanted); 4386 } 4387 return operand; 4388 } 4389 4390 if (op_bits > 32 and op_bits <= 64 and wanted_bits == 32) { 4391 try func.emitWValue(operand); 4392 try func.addTag(.i32_wrap_i64); 4393 if (given.isSignedInt(mod) and wanted_bitsize < 32) 4394 return func.wrapOperand(.{ .stack = {} }, wanted) 4395 else 4396 return WValue{ .stack = {} }; 4397 } else if (op_bits == 32 and wanted_bits > 32 and wanted_bits <= 64) { 4398 const operand32 = if (given_bitsize < 32 and wanted.isSignedInt(mod)) 4399 try func.signExtendInt(operand, given) 4400 else 4401 operand; 4402 try func.emitWValue(operand32); 4403 try func.addTag(if (wanted.isSignedInt(mod)) .i64_extend_i32_s else .i64_extend_i32_u); 4404 if (given.isSignedInt(mod) and wanted_bitsize < 64) 4405 return func.wrapOperand(.{ .stack = {} }, wanted) 4406 else 4407 return WValue{ .stack = {} }; 4408 } else if (wanted_bits == 128) { 4409 // for 128bit integers we store the integer in the virtual stack, rather than a local 4410 const stack_ptr = try func.allocStack(wanted); 4411 try func.emitWValue(stack_ptr); 4412 4413 // for 32 bit integers, we first coerce the value into a 64 bit integer before storing it 4414 // meaning less store operations are required. 4415 const lhs = if (op_bits == 32) blk: { 4416 break :blk try func.intcast(operand, given, if (wanted.isSignedInt(mod)) Type.i64 else Type.u64); 4417 } else operand; 4418 4419 // store msb first 4420 try func.store(.{ .stack = {} }, lhs, Type.u64, 0 + stack_ptr.offset()); 4421 4422 // For signed integers we shift msb by 63 (64bit integer - 1 sign bit) and store remaining value 4423 if (wanted.isSignedInt(mod)) { 4424 try func.emitWValue(stack_ptr); 4425 const shr = try func.binOp(lhs, .{ .imm64 = 63 }, Type.i64, .shr); 4426 try func.store(.{ .stack = {} }, shr, Type.u64, 8 + stack_ptr.offset()); 4427 } else { 4428 // Ensure memory of lsb is zero'd 4429 try func.store(stack_ptr, .{ .imm64 = 0 }, Type.u64, 8); 4430 } 4431 return stack_ptr; 4432 } else return func.load(operand, wanted, 0); 4433 } 4434 4435 fn airIsNull(func: *CodeGen, inst: Air.Inst.Index, opcode: wasm.Opcode, op_kind: enum { value, ptr }) InnerError!void { 4436 const mod = func.bin_file.base.comp.module.?; 4437 const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 4438 const operand = try func.resolveInst(un_op); 4439 4440 const op_ty = func.typeOf(un_op); 4441 const optional_ty = if (op_kind == .ptr) op_ty.childType(mod) else op_ty; 4442 const is_null = try func.isNull(operand, optional_ty, opcode); 4443 const result = try is_null.toLocal(func, optional_ty); 4444 func.finishAir(inst, result, &.{un_op}); 4445 } 4446 4447 /// For a given type and operand, checks if it's considered `null`. 4448 /// NOTE: Leaves the result on the stack 4449 fn isNull(func: *CodeGen, operand: WValue, optional_ty: Type, opcode: wasm.Opcode) InnerError!WValue { 4450 const mod = func.bin_file.base.comp.module.?; 4451 try func.emitWValue(operand); 4452 const payload_ty = optional_ty.optionalChild(mod); 4453 if (!optional_ty.optionalReprIsPayload(mod)) { 4454 // When payload is zero-bits, we can treat operand as a value, rather than 4455 // a pointer to the stack value 4456 if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) { 4457 const offset = std.math.cast(u32, payload_ty.abiSize(mod)) orelse { 4458 return func.fail("Optional type {} too big to fit into stack frame", .{optional_ty.fmt(mod)}); 4459 }; 4460 try func.addMemArg(.i32_load8_u, .{ .offset = operand.offset() + offset, .alignment = 1 }); 4461 } 4462 } else if (payload_ty.isSlice(mod)) { 4463 switch (func.arch()) { 4464 .wasm32 => try func.addMemArg(.i32_load, .{ .offset = operand.offset(), .alignment = 4 }), 4465 .wasm64 => try func.addMemArg(.i64_load, .{ .offset = operand.offset(), .alignment = 8 }), 4466 else => unreachable, 4467 } 4468 } 4469 4470 // Compare the null value with '0' 4471 try func.addImm32(0); 4472 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); 4473 4474 return WValue{ .stack = {} }; 4475 } 4476 4477 fn airOptionalPayload(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4478 const mod = func.bin_file.base.comp.module.?; 4479 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4480 const opt_ty = func.typeOf(ty_op.operand); 4481 const payload_ty = func.typeOfIndex(inst); 4482 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) { 4483 return func.finishAir(inst, .none, &.{ty_op.operand}); 4484 } 4485 4486 const result = result: { 4487 const operand = try func.resolveInst(ty_op.operand); 4488 if (opt_ty.optionalReprIsPayload(mod)) break :result func.reuseOperand(ty_op.operand, operand); 4489 4490 if (isByRef(payload_ty, mod)) { 4491 break :result try func.buildPointerOffset(operand, 0, .new); 4492 } 4493 4494 const payload = try func.load(operand, payload_ty, 0); 4495 break :result try payload.toLocal(func, payload_ty); 4496 }; 4497 func.finishAir(inst, result, &.{ty_op.operand}); 4498 } 4499 4500 fn airOptionalPayloadPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4501 const mod = func.bin_file.base.comp.module.?; 4502 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4503 const operand = try func.resolveInst(ty_op.operand); 4504 const opt_ty = func.typeOf(ty_op.operand).childType(mod); 4505 4506 const result = result: { 4507 const payload_ty = opt_ty.optionalChild(mod); 4508 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod) or opt_ty.optionalReprIsPayload(mod)) { 4509 break :result func.reuseOperand(ty_op.operand, operand); 4510 } 4511 4512 break :result try func.buildPointerOffset(operand, 0, .new); 4513 }; 4514 func.finishAir(inst, result, &.{ty_op.operand}); 4515 } 4516 4517 fn airOptionalPayloadPtrSet(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4518 const mod = func.bin_file.base.comp.module.?; 4519 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4520 const operand = try func.resolveInst(ty_op.operand); 4521 const opt_ty = func.typeOf(ty_op.operand).childType(mod); 4522 const payload_ty = opt_ty.optionalChild(mod); 4523 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) { 4524 return func.fail("TODO: Implement OptionalPayloadPtrSet for optional with zero-sized type {}", .{payload_ty.fmtDebug()}); 4525 } 4526 4527 if (opt_ty.optionalReprIsPayload(mod)) { 4528 return func.finishAir(inst, operand, &.{ty_op.operand}); 4529 } 4530 4531 const offset = std.math.cast(u32, payload_ty.abiSize(mod)) orelse { 4532 return func.fail("Optional type {} too big to fit into stack frame", .{opt_ty.fmt(mod)}); 4533 }; 4534 4535 try func.emitWValue(operand); 4536 try func.addImm32(1); 4537 try func.addMemArg(.i32_store8, .{ .offset = operand.offset() + offset, .alignment = 1 }); 4538 4539 const result = try func.buildPointerOffset(operand, 0, .new); 4540 return func.finishAir(inst, result, &.{ty_op.operand}); 4541 } 4542 4543 fn airWrapOptional(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4544 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4545 const payload_ty = func.typeOf(ty_op.operand); 4546 const mod = func.bin_file.base.comp.module.?; 4547 4548 const result = result: { 4549 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) { 4550 const non_null_bit = try func.allocStack(Type.u1); 4551 try func.emitWValue(non_null_bit); 4552 try func.addImm32(1); 4553 try func.addMemArg(.i32_store8, .{ .offset = non_null_bit.offset(), .alignment = 1 }); 4554 break :result non_null_bit; 4555 } 4556 4557 const operand = try func.resolveInst(ty_op.operand); 4558 const op_ty = func.typeOfIndex(inst); 4559 if (op_ty.optionalReprIsPayload(mod)) { 4560 break :result func.reuseOperand(ty_op.operand, operand); 4561 } 4562 const offset = std.math.cast(u32, payload_ty.abiSize(mod)) orelse { 4563 return func.fail("Optional type {} too big to fit into stack frame", .{op_ty.fmt(mod)}); 4564 }; 4565 4566 // Create optional type, set the non-null bit, and store the operand inside the optional type 4567 const result_ptr = try func.allocStack(op_ty); 4568 try func.emitWValue(result_ptr); 4569 try func.addImm32(1); 4570 try func.addMemArg(.i32_store8, .{ .offset = result_ptr.offset() + offset, .alignment = 1 }); 4571 4572 const payload_ptr = try func.buildPointerOffset(result_ptr, 0, .new); 4573 try func.store(payload_ptr, operand, payload_ty, 0); 4574 break :result result_ptr; 4575 }; 4576 4577 func.finishAir(inst, result, &.{ty_op.operand}); 4578 } 4579 4580 fn airSlice(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4581 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 4582 const bin_op = func.air.extraData(Air.Bin, ty_pl.payload).data; 4583 4584 const lhs = try func.resolveInst(bin_op.lhs); 4585 const rhs = try func.resolveInst(bin_op.rhs); 4586 const slice_ty = func.typeOfIndex(inst); 4587 4588 const slice = try func.allocStack(slice_ty); 4589 try func.store(slice, lhs, Type.usize, 0); 4590 try func.store(slice, rhs, Type.usize, func.ptrSize()); 4591 4592 func.finishAir(inst, slice, &.{ bin_op.lhs, bin_op.rhs }); 4593 } 4594 4595 fn airSliceLen(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4596 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4597 4598 const operand = try func.resolveInst(ty_op.operand); 4599 func.finishAir(inst, try func.sliceLen(operand), &.{ty_op.operand}); 4600 } 4601 4602 fn airSliceElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4603 const mod = func.bin_file.base.comp.module.?; 4604 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 4605 4606 const slice_ty = func.typeOf(bin_op.lhs); 4607 const slice = try func.resolveInst(bin_op.lhs); 4608 const index = try func.resolveInst(bin_op.rhs); 4609 const elem_ty = slice_ty.childType(mod); 4610 const elem_size = elem_ty.abiSize(mod); 4611 4612 // load pointer onto stack 4613 _ = try func.load(slice, Type.usize, 0); 4614 4615 // calculate index into slice 4616 try func.emitWValue(index); 4617 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(elem_size))))); 4618 try func.addTag(.i32_mul); 4619 try func.addTag(.i32_add); 4620 4621 const result_ptr = try func.allocLocal(Type.usize); 4622 try func.addLabel(.local_set, result_ptr.local.value); 4623 4624 const result = if (!isByRef(elem_ty, mod)) result: { 4625 const elem_val = try func.load(result_ptr, elem_ty, 0); 4626 break :result try elem_val.toLocal(func, elem_ty); 4627 } else result_ptr; 4628 4629 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 4630 } 4631 4632 fn airSliceElemPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4633 const mod = func.bin_file.base.comp.module.?; 4634 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 4635 const bin_op = func.air.extraData(Air.Bin, ty_pl.payload).data; 4636 4637 const elem_ty = ty_pl.ty.toType().childType(mod); 4638 const elem_size = elem_ty.abiSize(mod); 4639 4640 const slice = try func.resolveInst(bin_op.lhs); 4641 const index = try func.resolveInst(bin_op.rhs); 4642 4643 _ = try func.load(slice, Type.usize, 0); 4644 4645 // calculate index into slice 4646 try func.emitWValue(index); 4647 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(elem_size))))); 4648 try func.addTag(.i32_mul); 4649 try func.addTag(.i32_add); 4650 4651 const result = try func.allocLocal(Type.i32); 4652 try func.addLabel(.local_set, result.local.value); 4653 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 4654 } 4655 4656 fn airSlicePtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4657 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4658 const operand = try func.resolveInst(ty_op.operand); 4659 func.finishAir(inst, try func.slicePtr(operand), &.{ty_op.operand}); 4660 } 4661 4662 fn slicePtr(func: *CodeGen, operand: WValue) InnerError!WValue { 4663 const ptr = try func.load(operand, Type.usize, 0); 4664 return ptr.toLocal(func, Type.usize); 4665 } 4666 4667 fn sliceLen(func: *CodeGen, operand: WValue) InnerError!WValue { 4668 const len = try func.load(operand, Type.usize, func.ptrSize()); 4669 return len.toLocal(func, Type.usize); 4670 } 4671 4672 fn airTrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4673 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4674 4675 const operand = try func.resolveInst(ty_op.operand); 4676 const wanted_ty = ty_op.ty.toType(); 4677 const op_ty = func.typeOf(ty_op.operand); 4678 4679 const result = try func.trunc(operand, wanted_ty, op_ty); 4680 func.finishAir(inst, try result.toLocal(func, wanted_ty), &.{ty_op.operand}); 4681 } 4682 4683 /// Truncates a given operand to a given type, discarding any overflown bits. 4684 /// NOTE: Resulting value is left on the stack. 4685 fn trunc(func: *CodeGen, operand: WValue, wanted_ty: Type, given_ty: Type) InnerError!WValue { 4686 const mod = func.bin_file.base.comp.module.?; 4687 const given_bits = @as(u16, @intCast(given_ty.bitSize(mod))); 4688 if (toWasmBits(given_bits) == null) { 4689 return func.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{given_bits}); 4690 } 4691 4692 var result = try func.intcast(operand, given_ty, wanted_ty); 4693 const wanted_bits = @as(u16, @intCast(wanted_ty.bitSize(mod))); 4694 const wasm_bits = toWasmBits(wanted_bits).?; 4695 if (wasm_bits != wanted_bits) { 4696 result = try func.wrapOperand(result, wanted_ty); 4697 } 4698 return result; 4699 } 4700 4701 fn airIntFromBool(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4702 const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 4703 const operand = try func.resolveInst(un_op); 4704 const result = func.reuseOperand(un_op, operand); 4705 4706 func.finishAir(inst, result, &.{un_op}); 4707 } 4708 4709 fn airArrayToSlice(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4710 const mod = func.bin_file.base.comp.module.?; 4711 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4712 4713 const operand = try func.resolveInst(ty_op.operand); 4714 const array_ty = func.typeOf(ty_op.operand).childType(mod); 4715 const slice_ty = ty_op.ty.toType(); 4716 4717 // create a slice on the stack 4718 const slice_local = try func.allocStack(slice_ty); 4719 4720 // store the array ptr in the slice 4721 if (array_ty.hasRuntimeBitsIgnoreComptime(mod)) { 4722 try func.store(slice_local, operand, Type.usize, 0); 4723 } 4724 4725 // store the length of the array in the slice 4726 const len = WValue{ .imm32 = @as(u32, @intCast(array_ty.arrayLen(mod))) }; 4727 try func.store(slice_local, len, Type.usize, func.ptrSize()); 4728 4729 func.finishAir(inst, slice_local, &.{ty_op.operand}); 4730 } 4731 4732 fn airIntFromPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4733 const mod = func.bin_file.base.comp.module.?; 4734 const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 4735 const operand = try func.resolveInst(un_op); 4736 const ptr_ty = func.typeOf(un_op); 4737 const result = if (ptr_ty.isSlice(mod)) 4738 try func.slicePtr(operand) 4739 else switch (operand) { 4740 // for stack offset, return a pointer to this offset. 4741 .stack_offset => try func.buildPointerOffset(operand, 0, .new), 4742 else => func.reuseOperand(un_op, operand), 4743 }; 4744 func.finishAir(inst, result, &.{un_op}); 4745 } 4746 4747 fn airPtrElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4748 const mod = func.bin_file.base.comp.module.?; 4749 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 4750 4751 const ptr_ty = func.typeOf(bin_op.lhs); 4752 const ptr = try func.resolveInst(bin_op.lhs); 4753 const index = try func.resolveInst(bin_op.rhs); 4754 const elem_ty = ptr_ty.childType(mod); 4755 const elem_size = elem_ty.abiSize(mod); 4756 4757 // load pointer onto the stack 4758 if (ptr_ty.isSlice(mod)) { 4759 _ = try func.load(ptr, Type.usize, 0); 4760 } else { 4761 try func.lowerToStack(ptr); 4762 } 4763 4764 // calculate index into slice 4765 try func.emitWValue(index); 4766 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(elem_size))))); 4767 try func.addTag(.i32_mul); 4768 try func.addTag(.i32_add); 4769 4770 const elem_result = val: { 4771 var result = try func.allocLocal(Type.usize); 4772 try func.addLabel(.local_set, result.local.value); 4773 if (isByRef(elem_ty, mod)) { 4774 break :val result; 4775 } 4776 defer result.free(func); // only free if it's not returned like above 4777 4778 const elem_val = try func.load(result, elem_ty, 0); 4779 break :val try elem_val.toLocal(func, elem_ty); 4780 }; 4781 func.finishAir(inst, elem_result, &.{ bin_op.lhs, bin_op.rhs }); 4782 } 4783 4784 fn airPtrElemPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4785 const mod = func.bin_file.base.comp.module.?; 4786 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 4787 const bin_op = func.air.extraData(Air.Bin, ty_pl.payload).data; 4788 4789 const ptr_ty = func.typeOf(bin_op.lhs); 4790 const elem_ty = ty_pl.ty.toType().childType(mod); 4791 const elem_size = elem_ty.abiSize(mod); 4792 4793 const ptr = try func.resolveInst(bin_op.lhs); 4794 const index = try func.resolveInst(bin_op.rhs); 4795 4796 // load pointer onto the stack 4797 if (ptr_ty.isSlice(mod)) { 4798 _ = try func.load(ptr, Type.usize, 0); 4799 } else { 4800 try func.lowerToStack(ptr); 4801 } 4802 4803 // calculate index into ptr 4804 try func.emitWValue(index); 4805 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(elem_size))))); 4806 try func.addTag(.i32_mul); 4807 try func.addTag(.i32_add); 4808 4809 const result = try func.allocLocal(Type.i32); 4810 try func.addLabel(.local_set, result.local.value); 4811 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 4812 } 4813 4814 fn airPtrBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void { 4815 const mod = func.bin_file.base.comp.module.?; 4816 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 4817 const bin_op = func.air.extraData(Air.Bin, ty_pl.payload).data; 4818 4819 const ptr = try func.resolveInst(bin_op.lhs); 4820 const offset = try func.resolveInst(bin_op.rhs); 4821 const ptr_ty = func.typeOf(bin_op.lhs); 4822 const pointee_ty = switch (ptr_ty.ptrSize(mod)) { 4823 .One => ptr_ty.childType(mod).childType(mod), // ptr to array, so get array element type 4824 else => ptr_ty.childType(mod), 4825 }; 4826 4827 const valtype = typeToValtype(Type.usize, mod); 4828 const mul_opcode = buildOpcode(.{ .valtype1 = valtype, .op = .mul }); 4829 const bin_opcode = buildOpcode(.{ .valtype1 = valtype, .op = op }); 4830 4831 try func.lowerToStack(ptr); 4832 try func.emitWValue(offset); 4833 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(pointee_ty.abiSize(mod)))))); 4834 try func.addTag(Mir.Inst.Tag.fromOpcode(mul_opcode)); 4835 try func.addTag(Mir.Inst.Tag.fromOpcode(bin_opcode)); 4836 4837 const result = try func.allocLocal(Type.usize); 4838 try func.addLabel(.local_set, result.local.value); 4839 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 4840 } 4841 4842 fn airMemset(func: *CodeGen, inst: Air.Inst.Index, safety: bool) InnerError!void { 4843 const mod = func.bin_file.base.comp.module.?; 4844 if (safety) { 4845 // TODO if the value is undef, write 0xaa bytes to dest 4846 } else { 4847 // TODO if the value is undef, don't lower this instruction 4848 } 4849 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 4850 4851 const ptr = try func.resolveInst(bin_op.lhs); 4852 const ptr_ty = func.typeOf(bin_op.lhs); 4853 const value = try func.resolveInst(bin_op.rhs); 4854 const len = switch (ptr_ty.ptrSize(mod)) { 4855 .Slice => try func.sliceLen(ptr), 4856 .One => @as(WValue, .{ .imm32 = @as(u32, @intCast(ptr_ty.childType(mod).arrayLen(mod))) }), 4857 .C, .Many => unreachable, 4858 }; 4859 4860 const elem_ty = if (ptr_ty.ptrSize(mod) == .One) 4861 ptr_ty.childType(mod).childType(mod) 4862 else 4863 ptr_ty.childType(mod); 4864 4865 const dst_ptr = try func.sliceOrArrayPtr(ptr, ptr_ty); 4866 try func.memset(elem_ty, dst_ptr, len, value); 4867 4868 func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); 4869 } 4870 4871 /// Sets a region of memory at `ptr` to the value of `value` 4872 /// When the user has enabled the bulk_memory feature, we lower 4873 /// this to wasm's memset instruction. When the feature is not present, 4874 /// we implement it manually. 4875 fn memset(func: *CodeGen, elem_ty: Type, ptr: WValue, len: WValue, value: WValue) InnerError!void { 4876 const mod = func.bin_file.base.comp.module.?; 4877 const abi_size = @as(u32, @intCast(elem_ty.abiSize(mod))); 4878 4879 // When bulk_memory is enabled, we lower it to wasm's memset instruction. 4880 // If not, we lower it ourselves. 4881 if (std.Target.wasm.featureSetHas(func.target.cpu.features, .bulk_memory) and abi_size == 1) { 4882 try func.lowerToStack(ptr); 4883 try func.emitWValue(value); 4884 try func.emitWValue(len); 4885 try func.addExtended(.memory_fill); 4886 return; 4887 } 4888 4889 const final_len = switch (len) { 4890 .imm32 => |val| WValue{ .imm32 = val * abi_size }, 4891 .imm64 => |val| WValue{ .imm64 = val * abi_size }, 4892 else => if (abi_size != 1) blk: { 4893 const new_len = try func.ensureAllocLocal(Type.usize); 4894 try func.emitWValue(len); 4895 switch (func.arch()) { 4896 .wasm32 => { 4897 try func.emitWValue(.{ .imm32 = abi_size }); 4898 try func.addTag(.i32_mul); 4899 }, 4900 .wasm64 => { 4901 try func.emitWValue(.{ .imm64 = abi_size }); 4902 try func.addTag(.i64_mul); 4903 }, 4904 else => unreachable, 4905 } 4906 try func.addLabel(.local_set, new_len.local.value); 4907 break :blk new_len; 4908 } else len, 4909 }; 4910 4911 var end_ptr = try func.allocLocal(Type.usize); 4912 defer end_ptr.free(func); 4913 var new_ptr = try func.buildPointerOffset(ptr, 0, .new); 4914 defer new_ptr.free(func); 4915 4916 // get the loop conditional: if current pointer address equals final pointer's address 4917 try func.lowerToStack(ptr); 4918 try func.emitWValue(final_len); 4919 switch (func.arch()) { 4920 .wasm32 => try func.addTag(.i32_add), 4921 .wasm64 => try func.addTag(.i64_add), 4922 else => unreachable, 4923 } 4924 try func.addLabel(.local_set, end_ptr.local.value); 4925 4926 // outer block to jump to when loop is done 4927 try func.startBlock(.block, wasm.block_empty); 4928 try func.startBlock(.loop, wasm.block_empty); 4929 4930 // check for codition for loop end 4931 try func.emitWValue(new_ptr); 4932 try func.emitWValue(end_ptr); 4933 switch (func.arch()) { 4934 .wasm32 => try func.addTag(.i32_eq), 4935 .wasm64 => try func.addTag(.i64_eq), 4936 else => unreachable, 4937 } 4938 try func.addLabel(.br_if, 1); // jump out of loop into outer block (finished) 4939 4940 // store the value at the current position of the pointer 4941 try func.store(new_ptr, value, elem_ty, 0); 4942 4943 // move the pointer to the next element 4944 try func.emitWValue(new_ptr); 4945 switch (func.arch()) { 4946 .wasm32 => { 4947 try func.emitWValue(.{ .imm32 = abi_size }); 4948 try func.addTag(.i32_add); 4949 }, 4950 .wasm64 => { 4951 try func.emitWValue(.{ .imm64 = abi_size }); 4952 try func.addTag(.i64_add); 4953 }, 4954 else => unreachable, 4955 } 4956 try func.addLabel(.local_set, new_ptr.local.value); 4957 4958 // end of loop 4959 try func.addLabel(.br, 0); // jump to start of loop 4960 try func.endBlock(); 4961 try func.endBlock(); 4962 } 4963 4964 fn airArrayElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 4965 const mod = func.bin_file.base.comp.module.?; 4966 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 4967 4968 const array_ty = func.typeOf(bin_op.lhs); 4969 const array = try func.resolveInst(bin_op.lhs); 4970 const index = try func.resolveInst(bin_op.rhs); 4971 const elem_ty = array_ty.childType(mod); 4972 const elem_size = elem_ty.abiSize(mod); 4973 4974 if (isByRef(array_ty, mod)) { 4975 try func.lowerToStack(array); 4976 try func.emitWValue(index); 4977 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(elem_size))))); 4978 try func.addTag(.i32_mul); 4979 try func.addTag(.i32_add); 4980 } else { 4981 std.debug.assert(array_ty.zigTypeTag(mod) == .Vector); 4982 4983 switch (index) { 4984 inline .imm32, .imm64 => |lane| { 4985 const opcode: wasm.SimdOpcode = switch (elem_ty.bitSize(mod)) { 4986 8 => if (elem_ty.isSignedInt(mod)) .i8x16_extract_lane_s else .i8x16_extract_lane_u, 4987 16 => if (elem_ty.isSignedInt(mod)) .i16x8_extract_lane_s else .i16x8_extract_lane_u, 4988 32 => if (elem_ty.isInt(mod)) .i32x4_extract_lane else .f32x4_extract_lane, 4989 64 => if (elem_ty.isInt(mod)) .i64x2_extract_lane else .f64x2_extract_lane, 4990 else => unreachable, 4991 }; 4992 4993 var operands = [_]u32{ std.wasm.simdOpcode(opcode), @as(u8, @intCast(lane)) }; 4994 4995 try func.emitWValue(array); 4996 4997 const extra_index = @as(u32, @intCast(func.mir_extra.items.len)); 4998 try func.mir_extra.appendSlice(func.gpa, &operands); 4999 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } }); 5000 5001 return func.finishAir(inst, try WValue.toLocal(.stack, func, elem_ty), &.{ bin_op.lhs, bin_op.rhs }); 5002 }, 5003 else => { 5004 const stack_vec = try func.allocStack(array_ty); 5005 try func.store(stack_vec, array, array_ty, 0); 5006 5007 // Is a non-unrolled vector (v128) 5008 try func.lowerToStack(stack_vec); 5009 try func.emitWValue(index); 5010 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(elem_size))))); 5011 try func.addTag(.i32_mul); 5012 try func.addTag(.i32_add); 5013 }, 5014 } 5015 } 5016 5017 const elem_result = val: { 5018 var result = try func.allocLocal(Type.usize); 5019 try func.addLabel(.local_set, result.local.value); 5020 5021 if (isByRef(elem_ty, mod)) { 5022 break :val result; 5023 } 5024 defer result.free(func); // only free if no longer needed and not returned like above 5025 5026 const elem_val = try func.load(result, elem_ty, 0); 5027 break :val try elem_val.toLocal(func, elem_ty); 5028 }; 5029 5030 func.finishAir(inst, elem_result, &.{ bin_op.lhs, bin_op.rhs }); 5031 } 5032 5033 fn airIntFromFloat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5034 const mod = func.bin_file.base.comp.module.?; 5035 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5036 5037 const operand = try func.resolveInst(ty_op.operand); 5038 const op_ty = func.typeOf(ty_op.operand); 5039 const op_bits = op_ty.floatBits(func.target); 5040 5041 const dest_ty = func.typeOfIndex(inst); 5042 const dest_info = dest_ty.intInfo(mod); 5043 5044 if (dest_info.bits > 128) { 5045 return func.fail("TODO: intFromFloat for integers/floats with bitsize {}", .{dest_info.bits}); 5046 } 5047 5048 if ((op_bits != 32 and op_bits != 64) or dest_info.bits > 64) { 5049 const dest_bitsize = if (dest_info.bits <= 16) 16 else std.math.ceilPowerOfTwoAssert(u16, dest_info.bits); 5050 5051 var fn_name_buf: [16]u8 = undefined; 5052 const fn_name = std.fmt.bufPrint(&fn_name_buf, "__fix{s}{s}f{s}i", .{ 5053 switch (dest_info.signedness) { 5054 .signed => "", 5055 .unsigned => "uns", 5056 }, 5057 target_util.compilerRtFloatAbbrev(op_bits), 5058 target_util.compilerRtIntAbbrev(dest_bitsize), 5059 }) catch unreachable; 5060 5061 const result = try (try func.callIntrinsic(fn_name, &.{op_ty.ip_index}, dest_ty, &.{operand})).toLocal(func, dest_ty); 5062 return func.finishAir(inst, result, &.{ty_op.operand}); 5063 } 5064 5065 try func.emitWValue(operand); 5066 const op = buildOpcode(.{ 5067 .op = .trunc, 5068 .valtype1 = typeToValtype(dest_ty, mod), 5069 .valtype2 = typeToValtype(op_ty, mod), 5070 .signedness = dest_info.signedness, 5071 }); 5072 try func.addTag(Mir.Inst.Tag.fromOpcode(op)); 5073 const wrapped = try func.wrapOperand(.{ .stack = {} }, dest_ty); 5074 const result = try wrapped.toLocal(func, dest_ty); 5075 func.finishAir(inst, result, &.{ty_op.operand}); 5076 } 5077 5078 fn airFloatFromInt(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5079 const mod = func.bin_file.base.comp.module.?; 5080 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5081 5082 const operand = try func.resolveInst(ty_op.operand); 5083 const op_ty = func.typeOf(ty_op.operand); 5084 const op_info = op_ty.intInfo(mod); 5085 5086 const dest_ty = func.typeOfIndex(inst); 5087 const dest_bits = dest_ty.floatBits(func.target); 5088 5089 if (op_info.bits > 128) { 5090 return func.fail("TODO: floatFromInt for integers/floats with bitsize {d} bits", .{op_info.bits}); 5091 } 5092 5093 if (op_info.bits > 64 or (dest_bits > 64 or dest_bits < 32)) { 5094 const op_bitsize = if (op_info.bits <= 16) 16 else std.math.ceilPowerOfTwoAssert(u16, op_info.bits); 5095 5096 var fn_name_buf: [16]u8 = undefined; 5097 const fn_name = std.fmt.bufPrint(&fn_name_buf, "__float{s}{s}i{s}f", .{ 5098 switch (op_info.signedness) { 5099 .signed => "", 5100 .unsigned => "un", 5101 }, 5102 target_util.compilerRtIntAbbrev(op_bitsize), 5103 target_util.compilerRtFloatAbbrev(dest_bits), 5104 }) catch unreachable; 5105 5106 const result = try (try func.callIntrinsic(fn_name, &.{op_ty.ip_index}, dest_ty, &.{operand})).toLocal(func, dest_ty); 5107 return func.finishAir(inst, result, &.{ty_op.operand}); 5108 } 5109 5110 try func.emitWValue(operand); 5111 const op = buildOpcode(.{ 5112 .op = .convert, 5113 .valtype1 = typeToValtype(dest_ty, mod), 5114 .valtype2 = typeToValtype(op_ty, mod), 5115 .signedness = op_info.signedness, 5116 }); 5117 try func.addTag(Mir.Inst.Tag.fromOpcode(op)); 5118 5119 const result = try func.allocLocal(dest_ty); 5120 try func.addLabel(.local_set, result.local.value); 5121 func.finishAir(inst, result, &.{ty_op.operand}); 5122 } 5123 5124 fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5125 const mod = func.bin_file.base.comp.module.?; 5126 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5127 const operand = try func.resolveInst(ty_op.operand); 5128 const ty = func.typeOfIndex(inst); 5129 const elem_ty = ty.childType(mod); 5130 5131 if (determineSimdStoreStrategy(ty, mod) == .direct) blk: { 5132 switch (operand) { 5133 // when the operand lives in the linear memory section, we can directly 5134 // load and splat the value at once. Meaning we do not first have to load 5135 // the scalar value onto the stack. 5136 .stack_offset, .memory, .memory_offset => { 5137 const opcode = switch (elem_ty.bitSize(mod)) { 5138 8 => std.wasm.simdOpcode(.v128_load8_splat), 5139 16 => std.wasm.simdOpcode(.v128_load16_splat), 5140 32 => std.wasm.simdOpcode(.v128_load32_splat), 5141 64 => std.wasm.simdOpcode(.v128_load64_splat), 5142 else => break :blk, // Cannot make use of simd-instructions 5143 }; 5144 const result = try func.allocLocal(ty); 5145 try func.emitWValue(operand); 5146 // TODO: Add helper functions for simd opcodes 5147 const extra_index = @as(u32, @intCast(func.mir_extra.items.len)); 5148 // stores as := opcode, offset, alignment (opcode::memarg) 5149 try func.mir_extra.appendSlice(func.gpa, &[_]u32{ 5150 opcode, 5151 operand.offset(), 5152 @intCast(elem_ty.abiAlignment(mod).toByteUnitsOptional().?), 5153 }); 5154 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } }); 5155 try func.addLabel(.local_set, result.local.value); 5156 return func.finishAir(inst, result, &.{ty_op.operand}); 5157 }, 5158 .local => { 5159 const opcode = switch (elem_ty.bitSize(mod)) { 5160 8 => std.wasm.simdOpcode(.i8x16_splat), 5161 16 => std.wasm.simdOpcode(.i16x8_splat), 5162 32 => if (elem_ty.isInt(mod)) std.wasm.simdOpcode(.i32x4_splat) else std.wasm.simdOpcode(.f32x4_splat), 5163 64 => if (elem_ty.isInt(mod)) std.wasm.simdOpcode(.i64x2_splat) else std.wasm.simdOpcode(.f64x2_splat), 5164 else => break :blk, // Cannot make use of simd-instructions 5165 }; 5166 const result = try func.allocLocal(ty); 5167 try func.emitWValue(operand); 5168 const extra_index = @as(u32, @intCast(func.mir_extra.items.len)); 5169 try func.mir_extra.append(func.gpa, opcode); 5170 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } }); 5171 try func.addLabel(.local_set, result.local.value); 5172 return func.finishAir(inst, result, &.{ty_op.operand}); 5173 }, 5174 else => unreachable, 5175 } 5176 } 5177 const elem_size = elem_ty.bitSize(mod); 5178 const vector_len = @as(usize, @intCast(ty.vectorLen(mod))); 5179 if ((!std.math.isPowerOfTwo(elem_size) or elem_size % 8 != 0) and vector_len > 1) { 5180 return func.fail("TODO: WebAssembly `@splat` for arbitrary element bitsize {d}", .{elem_size}); 5181 } 5182 5183 const result = try func.allocStack(ty); 5184 const elem_byte_size = @as(u32, @intCast(elem_ty.abiSize(mod))); 5185 var index: usize = 0; 5186 var offset: u32 = 0; 5187 while (index < vector_len) : (index += 1) { 5188 try func.store(result, operand, elem_ty, offset); 5189 offset += elem_byte_size; 5190 } 5191 5192 return func.finishAir(inst, result, &.{ty_op.operand}); 5193 } 5194 5195 fn airSelect(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5196 const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 5197 const operand = try func.resolveInst(pl_op.operand); 5198 5199 _ = operand; 5200 return func.fail("TODO: Implement wasm airSelect", .{}); 5201 } 5202 5203 fn airShuffle(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5204 const mod = func.bin_file.base.comp.module.?; 5205 const inst_ty = func.typeOfIndex(inst); 5206 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 5207 const extra = func.air.extraData(Air.Shuffle, ty_pl.payload).data; 5208 5209 const a = try func.resolveInst(extra.a); 5210 const b = try func.resolveInst(extra.b); 5211 const mask = Value.fromInterned(extra.mask); 5212 const mask_len = extra.mask_len; 5213 5214 const child_ty = inst_ty.childType(mod); 5215 const elem_size = child_ty.abiSize(mod); 5216 5217 // TODO: One of them could be by ref; handle in loop 5218 if (isByRef(func.typeOf(extra.a), mod) or isByRef(inst_ty, mod)) { 5219 const result = try func.allocStack(inst_ty); 5220 5221 for (0..mask_len) |index| { 5222 const value = (try mask.elemValue(mod, index)).toSignedInt(mod); 5223 5224 try func.emitWValue(result); 5225 5226 const loaded = if (value >= 0) 5227 try func.load(a, child_ty, @as(u32, @intCast(@as(i64, @intCast(elem_size)) * value))) 5228 else 5229 try func.load(b, child_ty, @as(u32, @intCast(@as(i64, @intCast(elem_size)) * ~value))); 5230 5231 try func.store(.stack, loaded, child_ty, result.stack_offset.value + @as(u32, @intCast(elem_size)) * @as(u32, @intCast(index))); 5232 } 5233 5234 return func.finishAir(inst, result, &.{ extra.a, extra.b }); 5235 } else { 5236 var operands = [_]u32{ 5237 std.wasm.simdOpcode(.i8x16_shuffle), 5238 } ++ [1]u32{undefined} ** 4; 5239 5240 var lanes = mem.asBytes(operands[1..]); 5241 for (0..@as(usize, @intCast(mask_len))) |index| { 5242 const mask_elem = (try mask.elemValue(mod, index)).toSignedInt(mod); 5243 const base_index = if (mask_elem >= 0) 5244 @as(u8, @intCast(@as(i64, @intCast(elem_size)) * mask_elem)) 5245 else 5246 16 + @as(u8, @intCast(@as(i64, @intCast(elem_size)) * ~mask_elem)); 5247 5248 for (0..@as(usize, @intCast(elem_size))) |byte_offset| { 5249 lanes[index * @as(usize, @intCast(elem_size)) + byte_offset] = base_index + @as(u8, @intCast(byte_offset)); 5250 } 5251 } 5252 5253 try func.emitWValue(a); 5254 try func.emitWValue(b); 5255 5256 const extra_index = @as(u32, @intCast(func.mir_extra.items.len)); 5257 try func.mir_extra.appendSlice(func.gpa, &operands); 5258 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } }); 5259 5260 return func.finishAir(inst, try WValue.toLocal(.stack, func, inst_ty), &.{ extra.a, extra.b }); 5261 } 5262 } 5263 5264 fn airReduce(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5265 const reduce = func.air.instructions.items(.data)[@intFromEnum(inst)].reduce; 5266 const operand = try func.resolveInst(reduce.operand); 5267 5268 _ = operand; 5269 return func.fail("TODO: Implement wasm airReduce", .{}); 5270 } 5271 5272 fn airAggregateInit(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5273 const mod = func.bin_file.base.comp.module.?; 5274 const ip = &mod.intern_pool; 5275 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 5276 const result_ty = func.typeOfIndex(inst); 5277 const len = @as(usize, @intCast(result_ty.arrayLen(mod))); 5278 const elements = @as([]const Air.Inst.Ref, @ptrCast(func.air.extra[ty_pl.payload..][0..len])); 5279 5280 const result: WValue = result_value: { 5281 switch (result_ty.zigTypeTag(mod)) { 5282 .Array => { 5283 const result = try func.allocStack(result_ty); 5284 const elem_ty = result_ty.childType(mod); 5285 const elem_size = @as(u32, @intCast(elem_ty.abiSize(mod))); 5286 const sentinel = if (result_ty.sentinel(mod)) |sent| blk: { 5287 break :blk try func.lowerConstant(sent, elem_ty); 5288 } else null; 5289 5290 // When the element type is by reference, we must copy the entire 5291 // value. It is therefore safer to move the offset pointer and store 5292 // each value individually, instead of using store offsets. 5293 if (isByRef(elem_ty, mod)) { 5294 // copy stack pointer into a temporary local, which is 5295 // moved for each element to store each value in the right position. 5296 const offset = try func.buildPointerOffset(result, 0, .new); 5297 for (elements, 0..) |elem, elem_index| { 5298 const elem_val = try func.resolveInst(elem); 5299 try func.store(offset, elem_val, elem_ty, 0); 5300 5301 if (elem_index < elements.len - 1 and sentinel == null) { 5302 _ = try func.buildPointerOffset(offset, elem_size, .modify); 5303 } 5304 } 5305 if (sentinel) |sent| { 5306 try func.store(offset, sent, elem_ty, 0); 5307 } 5308 } else { 5309 var offset: u32 = 0; 5310 for (elements) |elem| { 5311 const elem_val = try func.resolveInst(elem); 5312 try func.store(result, elem_val, elem_ty, offset); 5313 offset += elem_size; 5314 } 5315 if (sentinel) |sent| { 5316 try func.store(result, sent, elem_ty, offset); 5317 } 5318 } 5319 break :result_value result; 5320 }, 5321 .Struct => switch (result_ty.containerLayout(mod)) { 5322 .Packed => { 5323 if (isByRef(result_ty, mod)) { 5324 return func.fail("TODO: airAggregateInit for packed structs larger than 64 bits", .{}); 5325 } 5326 const packed_struct = mod.typeToPackedStruct(result_ty).?; 5327 const field_types = packed_struct.field_types; 5328 const backing_type = Type.fromInterned(packed_struct.backingIntType(ip).*); 5329 5330 // ensure the result is zero'd 5331 const result = try func.allocLocal(backing_type); 5332 if (backing_type.bitSize(mod) <= 32) 5333 try func.addImm32(0) 5334 else 5335 try func.addImm64(0); 5336 try func.addLabel(.local_set, result.local.value); 5337 5338 var current_bit: u16 = 0; 5339 for (elements, 0..) |elem, elem_index| { 5340 const field_ty = Type.fromInterned(field_types.get(ip)[elem_index]); 5341 if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue; 5342 5343 const shift_val = if (backing_type.bitSize(mod) <= 32) 5344 WValue{ .imm32 = current_bit } 5345 else 5346 WValue{ .imm64 = current_bit }; 5347 5348 const value = try func.resolveInst(elem); 5349 const value_bit_size: u16 = @intCast(field_ty.bitSize(mod)); 5350 const int_ty = try mod.intType(.unsigned, value_bit_size); 5351 5352 // load our current result on stack so we can perform all transformations 5353 // using only stack values. Saving the cost of loads and stores. 5354 try func.emitWValue(result); 5355 const bitcasted = try func.bitcast(int_ty, field_ty, value); 5356 const extended_val = try func.intcast(bitcasted, int_ty, backing_type); 5357 // no need to shift any values when the current offset is 0 5358 const shifted = if (current_bit != 0) shifted: { 5359 break :shifted try func.binOp(extended_val, shift_val, backing_type, .shl); 5360 } else extended_val; 5361 // we ignore the result as we keep it on the stack to assign it directly to `result` 5362 _ = try func.binOp(.stack, shifted, backing_type, .@"or"); 5363 try func.addLabel(.local_set, result.local.value); 5364 current_bit += value_bit_size; 5365 } 5366 break :result_value result; 5367 }, 5368 else => { 5369 const result = try func.allocStack(result_ty); 5370 const offset = try func.buildPointerOffset(result, 0, .new); // pointer to offset 5371 var prev_field_offset: u64 = 0; 5372 for (elements, 0..) |elem, elem_index| { 5373 if ((try result_ty.structFieldValueComptime(mod, elem_index)) != null) continue; 5374 5375 const elem_ty = result_ty.structFieldType(elem_index, mod); 5376 const field_offset = result_ty.structFieldOffset(elem_index, mod); 5377 _ = try func.buildPointerOffset(offset, @intCast(field_offset - prev_field_offset), .modify); 5378 prev_field_offset = field_offset; 5379 5380 const value = try func.resolveInst(elem); 5381 try func.store(offset, value, elem_ty, 0); 5382 } 5383 5384 break :result_value result; 5385 }, 5386 }, 5387 .Vector => return func.fail("TODO: Wasm backend: implement airAggregateInit for vectors", .{}), 5388 else => unreachable, 5389 } 5390 }; 5391 5392 if (elements.len <= Liveness.bpi - 1) { 5393 var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1); 5394 @memcpy(buf[0..elements.len], elements); 5395 return func.finishAir(inst, result, &buf); 5396 } 5397 var bt = try func.iterateBigTomb(inst, elements.len); 5398 for (elements) |arg| bt.feed(arg); 5399 return bt.finishAir(result); 5400 } 5401 5402 fn airUnionInit(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5403 const mod = func.bin_file.base.comp.module.?; 5404 const ip = &mod.intern_pool; 5405 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 5406 const extra = func.air.extraData(Air.UnionInit, ty_pl.payload).data; 5407 5408 const result = result: { 5409 const union_ty = func.typeOfIndex(inst); 5410 const layout = union_ty.unionGetLayout(mod); 5411 const union_obj = mod.typeToUnion(union_ty).?; 5412 const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[extra.field_index]); 5413 const field_name = union_obj.field_names.get(ip)[extra.field_index]; 5414 5415 const tag_int = blk: { 5416 const tag_ty = union_ty.unionTagTypeHypothetical(mod); 5417 const enum_field_index = tag_ty.enumFieldIndex(field_name, mod).?; 5418 const tag_val = try mod.enumValueFieldIndex(tag_ty, enum_field_index); 5419 break :blk try func.lowerConstant(tag_val, tag_ty); 5420 }; 5421 if (layout.payload_size == 0) { 5422 if (layout.tag_size == 0) { 5423 break :result WValue{ .none = {} }; 5424 } 5425 assert(!isByRef(union_ty, mod)); 5426 break :result tag_int; 5427 } 5428 5429 if (isByRef(union_ty, mod)) { 5430 const result_ptr = try func.allocStack(union_ty); 5431 const payload = try func.resolveInst(extra.init); 5432 if (layout.tag_align.compare(.gte, layout.payload_align)) { 5433 if (isByRef(field_ty, mod)) { 5434 const payload_ptr = try func.buildPointerOffset(result_ptr, layout.tag_size, .new); 5435 try func.store(payload_ptr, payload, field_ty, 0); 5436 } else { 5437 try func.store(result_ptr, payload, field_ty, @intCast(layout.tag_size)); 5438 } 5439 5440 if (layout.tag_size > 0) { 5441 try func.store(result_ptr, tag_int, Type.fromInterned(union_obj.enum_tag_ty), 0); 5442 } 5443 } else { 5444 try func.store(result_ptr, payload, field_ty, 0); 5445 if (layout.tag_size > 0) { 5446 try func.store( 5447 result_ptr, 5448 tag_int, 5449 Type.fromInterned(union_obj.enum_tag_ty), 5450 @intCast(layout.payload_size), 5451 ); 5452 } 5453 } 5454 break :result result_ptr; 5455 } else { 5456 const operand = try func.resolveInst(extra.init); 5457 const union_int_type = try mod.intType(.unsigned, @as(u16, @intCast(union_ty.bitSize(mod)))); 5458 if (field_ty.zigTypeTag(mod) == .Float) { 5459 const int_type = try mod.intType(.unsigned, @intCast(field_ty.bitSize(mod))); 5460 const bitcasted = try func.bitcast(field_ty, int_type, operand); 5461 const casted = try func.trunc(bitcasted, int_type, union_int_type); 5462 break :result try casted.toLocal(func, field_ty); 5463 } else if (field_ty.isPtrAtRuntime(mod)) { 5464 const int_type = try mod.intType(.unsigned, @intCast(field_ty.bitSize(mod))); 5465 const casted = try func.intcast(operand, int_type, union_int_type); 5466 break :result try casted.toLocal(func, field_ty); 5467 } 5468 const casted = try func.intcast(operand, field_ty, union_int_type); 5469 break :result try casted.toLocal(func, field_ty); 5470 } 5471 }; 5472 5473 return func.finishAir(inst, result, &.{extra.init}); 5474 } 5475 5476 fn airPrefetch(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5477 const prefetch = func.air.instructions.items(.data)[@intFromEnum(inst)].prefetch; 5478 func.finishAir(inst, .none, &.{prefetch.ptr}); 5479 } 5480 5481 fn airWasmMemorySize(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5482 const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 5483 5484 const result = try func.allocLocal(func.typeOfIndex(inst)); 5485 try func.addLabel(.memory_size, pl_op.payload); 5486 try func.addLabel(.local_set, result.local.value); 5487 func.finishAir(inst, result, &.{pl_op.operand}); 5488 } 5489 5490 fn airWasmMemoryGrow(func: *CodeGen, inst: Air.Inst.Index) !void { 5491 const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 5492 5493 const operand = try func.resolveInst(pl_op.operand); 5494 const result = try func.allocLocal(func.typeOfIndex(inst)); 5495 try func.emitWValue(operand); 5496 try func.addLabel(.memory_grow, pl_op.payload); 5497 try func.addLabel(.local_set, result.local.value); 5498 func.finishAir(inst, result, &.{pl_op.operand}); 5499 } 5500 5501 fn cmpOptionals(func: *CodeGen, lhs: WValue, rhs: WValue, operand_ty: Type, op: std.math.CompareOperator) InnerError!WValue { 5502 const mod = func.bin_file.base.comp.module.?; 5503 assert(operand_ty.hasRuntimeBitsIgnoreComptime(mod)); 5504 assert(op == .eq or op == .neq); 5505 const payload_ty = operand_ty.optionalChild(mod); 5506 5507 // We store the final result in here that will be validated 5508 // if the optional is truly equal. 5509 var result = try func.ensureAllocLocal(Type.i32); 5510 defer result.free(func); 5511 5512 try func.startBlock(.block, wasm.block_empty); 5513 _ = try func.isNull(lhs, operand_ty, .i32_eq); 5514 _ = try func.isNull(rhs, operand_ty, .i32_eq); 5515 try func.addTag(.i32_ne); // inverse so we can exit early 5516 try func.addLabel(.br_if, 0); 5517 5518 _ = try func.load(lhs, payload_ty, 0); 5519 _ = try func.load(rhs, payload_ty, 0); 5520 const opcode = buildOpcode(.{ .op = .ne, .valtype1 = typeToValtype(payload_ty, mod) }); 5521 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); 5522 try func.addLabel(.br_if, 0); 5523 5524 try func.addImm32(1); 5525 try func.addLabel(.local_set, result.local.value); 5526 try func.endBlock(); 5527 5528 try func.emitWValue(result); 5529 try func.addImm32(0); 5530 try func.addTag(if (op == .eq) .i32_ne else .i32_eq); 5531 return WValue{ .stack = {} }; 5532 } 5533 5534 /// Compares big integers by checking both its high bits and low bits. 5535 /// NOTE: Leaves the result of the comparison on top of the stack. 5536 /// TODO: Lower this to compiler_rt call when bitsize > 128 5537 fn cmpBigInt(func: *CodeGen, lhs: WValue, rhs: WValue, operand_ty: Type, op: std.math.CompareOperator) InnerError!WValue { 5538 const mod = func.bin_file.base.comp.module.?; 5539 assert(operand_ty.abiSize(mod) >= 16); 5540 assert(!(lhs != .stack and rhs == .stack)); 5541 if (operand_ty.bitSize(mod) > 128) { 5542 return func.fail("TODO: Support cmpBigInt for integer bitsize: '{d}'", .{operand_ty.bitSize(mod)}); 5543 } 5544 5545 var lhs_high_bit = try (try func.load(lhs, Type.u64, 0)).toLocal(func, Type.u64); 5546 defer lhs_high_bit.free(func); 5547 var rhs_high_bit = try (try func.load(rhs, Type.u64, 0)).toLocal(func, Type.u64); 5548 defer rhs_high_bit.free(func); 5549 5550 switch (op) { 5551 .eq, .neq => { 5552 const xor_high = try func.binOp(lhs_high_bit, rhs_high_bit, Type.u64, .xor); 5553 const lhs_low_bit = try func.load(lhs, Type.u64, 8); 5554 const rhs_low_bit = try func.load(rhs, Type.u64, 8); 5555 const xor_low = try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, .xor); 5556 const or_result = try func.binOp(xor_high, xor_low, Type.u64, .@"or"); 5557 5558 switch (op) { 5559 .eq => return func.cmp(or_result, .{ .imm64 = 0 }, Type.u64, .eq), 5560 .neq => return func.cmp(or_result, .{ .imm64 = 0 }, Type.u64, .neq), 5561 else => unreachable, 5562 } 5563 }, 5564 else => { 5565 const ty = if (operand_ty.isSignedInt(mod)) Type.i64 else Type.u64; 5566 // leave those value on top of the stack for '.select' 5567 const lhs_low_bit = try func.load(lhs, Type.u64, 8); 5568 const rhs_low_bit = try func.load(rhs, Type.u64, 8); 5569 _ = try func.cmp(lhs_low_bit, rhs_low_bit, ty, op); 5570 _ = try func.cmp(lhs_high_bit, rhs_high_bit, ty, op); 5571 _ = try func.cmp(lhs_high_bit, rhs_high_bit, ty, .eq); 5572 try func.addTag(.select); 5573 }, 5574 } 5575 5576 return WValue{ .stack = {} }; 5577 } 5578 5579 fn airSetUnionTag(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5580 const mod = func.bin_file.base.comp.module.?; 5581 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 5582 const un_ty = func.typeOf(bin_op.lhs).childType(mod); 5583 const tag_ty = func.typeOf(bin_op.rhs); 5584 const layout = un_ty.unionGetLayout(mod); 5585 if (layout.tag_size == 0) return func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); 5586 5587 const union_ptr = try func.resolveInst(bin_op.lhs); 5588 const new_tag = try func.resolveInst(bin_op.rhs); 5589 if (layout.payload_size == 0) { 5590 try func.store(union_ptr, new_tag, tag_ty, 0); 5591 return func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); 5592 } 5593 5594 // when the tag alignment is smaller than the payload, the field will be stored 5595 // after the payload. 5596 const offset: u32 = if (layout.tag_align.compare(.lt, layout.payload_align)) blk: { 5597 break :blk @intCast(layout.payload_size); 5598 } else 0; 5599 try func.store(union_ptr, new_tag, tag_ty, offset); 5600 func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); 5601 } 5602 5603 fn airGetUnionTag(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5604 const mod = func.bin_file.base.comp.module.?; 5605 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5606 5607 const un_ty = func.typeOf(ty_op.operand); 5608 const tag_ty = func.typeOfIndex(inst); 5609 const layout = un_ty.unionGetLayout(mod); 5610 if (layout.tag_size == 0) return func.finishAir(inst, .none, &.{ty_op.operand}); 5611 5612 const operand = try func.resolveInst(ty_op.operand); 5613 // when the tag alignment is smaller than the payload, the field will be stored 5614 // after the payload. 5615 const offset: u32 = if (layout.tag_align.compare(.lt, layout.payload_align)) blk: { 5616 break :blk @intCast(layout.payload_size); 5617 } else 0; 5618 const tag = try func.load(operand, tag_ty, offset); 5619 const result = try tag.toLocal(func, tag_ty); 5620 func.finishAir(inst, result, &.{ty_op.operand}); 5621 } 5622 5623 fn airFpext(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5624 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5625 5626 const dest_ty = func.typeOfIndex(inst); 5627 const operand = try func.resolveInst(ty_op.operand); 5628 const extended = try func.fpext(operand, func.typeOf(ty_op.operand), dest_ty); 5629 const result = try extended.toLocal(func, dest_ty); 5630 func.finishAir(inst, result, &.{ty_op.operand}); 5631 } 5632 5633 /// Extends a float from a given `Type` to a larger wanted `Type` 5634 /// NOTE: Leaves the result on the stack 5635 fn fpext(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError!WValue { 5636 const given_bits = given.floatBits(func.target); 5637 const wanted_bits = wanted.floatBits(func.target); 5638 5639 if (wanted_bits == 64 and given_bits == 32) { 5640 try func.emitWValue(operand); 5641 try func.addTag(.f64_promote_f32); 5642 return WValue{ .stack = {} }; 5643 } else if (given_bits == 16 and wanted_bits <= 64) { 5644 // call __extendhfsf2(f16) f32 5645 const f32_result = try func.callIntrinsic( 5646 "__extendhfsf2", 5647 &.{.f16_type}, 5648 Type.f32, 5649 &.{operand}, 5650 ); 5651 std.debug.assert(f32_result == .stack); 5652 5653 if (wanted_bits == 64) { 5654 try func.addTag(.f64_promote_f32); 5655 } 5656 return WValue{ .stack = {} }; 5657 } 5658 5659 var fn_name_buf: [13]u8 = undefined; 5660 const fn_name = std.fmt.bufPrint(&fn_name_buf, "__extend{s}f{s}f2", .{ 5661 target_util.compilerRtFloatAbbrev(given_bits), 5662 target_util.compilerRtFloatAbbrev(wanted_bits), 5663 }) catch unreachable; 5664 5665 return func.callIntrinsic(fn_name, &.{given.ip_index}, wanted, &.{operand}); 5666 } 5667 5668 fn airFptrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5669 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5670 5671 const dest_ty = func.typeOfIndex(inst); 5672 const operand = try func.resolveInst(ty_op.operand); 5673 const truncated = try func.fptrunc(operand, func.typeOf(ty_op.operand), dest_ty); 5674 const result = try truncated.toLocal(func, dest_ty); 5675 func.finishAir(inst, result, &.{ty_op.operand}); 5676 } 5677 5678 /// Truncates a float from a given `Type` to its wanted `Type` 5679 /// NOTE: The result value remains on the stack 5680 fn fptrunc(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError!WValue { 5681 const given_bits = given.floatBits(func.target); 5682 const wanted_bits = wanted.floatBits(func.target); 5683 5684 if (wanted_bits == 32 and given_bits == 64) { 5685 try func.emitWValue(operand); 5686 try func.addTag(.f32_demote_f64); 5687 return WValue{ .stack = {} }; 5688 } else if (wanted_bits == 16 and given_bits <= 64) { 5689 const op: WValue = if (given_bits == 64) blk: { 5690 try func.emitWValue(operand); 5691 try func.addTag(.f32_demote_f64); 5692 break :blk WValue{ .stack = {} }; 5693 } else operand; 5694 5695 // call __truncsfhf2(f32) f16 5696 return func.callIntrinsic("__truncsfhf2", &.{.f32_type}, Type.f16, &.{op}); 5697 } 5698 5699 var fn_name_buf: [12]u8 = undefined; 5700 const fn_name = std.fmt.bufPrint(&fn_name_buf, "__trunc{s}f{s}f2", .{ 5701 target_util.compilerRtFloatAbbrev(given_bits), 5702 target_util.compilerRtFloatAbbrev(wanted_bits), 5703 }) catch unreachable; 5704 5705 return func.callIntrinsic(fn_name, &.{given.ip_index}, wanted, &.{operand}); 5706 } 5707 5708 fn airErrUnionPayloadPtrSet(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5709 const mod = func.bin_file.base.comp.module.?; 5710 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5711 5712 const err_set_ty = func.typeOf(ty_op.operand).childType(mod); 5713 const payload_ty = err_set_ty.errorUnionPayload(mod); 5714 const operand = try func.resolveInst(ty_op.operand); 5715 5716 // set error-tag to '0' to annotate error union is non-error 5717 try func.store( 5718 operand, 5719 .{ .imm32 = 0 }, 5720 Type.anyerror, 5721 @as(u32, @intCast(errUnionErrorOffset(payload_ty, mod))), 5722 ); 5723 5724 const result = result: { 5725 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) { 5726 break :result func.reuseOperand(ty_op.operand, operand); 5727 } 5728 5729 break :result try func.buildPointerOffset(operand, @as(u32, @intCast(errUnionPayloadOffset(payload_ty, mod))), .new); 5730 }; 5731 func.finishAir(inst, result, &.{ty_op.operand}); 5732 } 5733 5734 fn airFieldParentPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5735 const mod = func.bin_file.base.comp.module.?; 5736 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 5737 const extra = func.air.extraData(Air.FieldParentPtr, ty_pl.payload).data; 5738 5739 const field_ptr = try func.resolveInst(extra.field_ptr); 5740 const parent_ty = ty_pl.ty.toType().childType(mod); 5741 const field_offset = parent_ty.structFieldOffset(extra.field_index, mod); 5742 5743 const result = if (field_offset != 0) result: { 5744 const base = try func.buildPointerOffset(field_ptr, 0, .new); 5745 try func.addLabel(.local_get, base.local.value); 5746 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(field_offset))))); 5747 try func.addTag(.i32_sub); 5748 try func.addLabel(.local_set, base.local.value); 5749 break :result base; 5750 } else func.reuseOperand(extra.field_ptr, field_ptr); 5751 5752 func.finishAir(inst, result, &.{extra.field_ptr}); 5753 } 5754 5755 fn sliceOrArrayPtr(func: *CodeGen, ptr: WValue, ptr_ty: Type) InnerError!WValue { 5756 const mod = func.bin_file.base.comp.module.?; 5757 if (ptr_ty.isSlice(mod)) { 5758 return func.slicePtr(ptr); 5759 } else { 5760 return ptr; 5761 } 5762 } 5763 5764 fn airMemcpy(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5765 const mod = func.bin_file.base.comp.module.?; 5766 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 5767 const dst = try func.resolveInst(bin_op.lhs); 5768 const dst_ty = func.typeOf(bin_op.lhs); 5769 const ptr_elem_ty = dst_ty.childType(mod); 5770 const src = try func.resolveInst(bin_op.rhs); 5771 const src_ty = func.typeOf(bin_op.rhs); 5772 const len = switch (dst_ty.ptrSize(mod)) { 5773 .Slice => blk: { 5774 const slice_len = try func.sliceLen(dst); 5775 if (ptr_elem_ty.abiSize(mod) != 1) { 5776 try func.emitWValue(slice_len); 5777 try func.emitWValue(.{ .imm32 = @as(u32, @intCast(ptr_elem_ty.abiSize(mod))) }); 5778 try func.addTag(.i32_mul); 5779 try func.addLabel(.local_set, slice_len.local.value); 5780 } 5781 break :blk slice_len; 5782 }, 5783 .One => @as(WValue, .{ 5784 .imm32 = @as(u32, @intCast(ptr_elem_ty.arrayLen(mod) * ptr_elem_ty.childType(mod).abiSize(mod))), 5785 }), 5786 .C, .Many => unreachable, 5787 }; 5788 const dst_ptr = try func.sliceOrArrayPtr(dst, dst_ty); 5789 const src_ptr = try func.sliceOrArrayPtr(src, src_ty); 5790 try func.memcpy(dst_ptr, src_ptr, len); 5791 5792 func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); 5793 } 5794 5795 fn airRetAddr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5796 // TODO: Implement this properly once stack serialization is solved 5797 func.finishAir(inst, switch (func.arch()) { 5798 .wasm32 => .{ .imm32 = 0 }, 5799 .wasm64 => .{ .imm64 = 0 }, 5800 else => unreachable, 5801 }, &.{}); 5802 } 5803 5804 fn airPopcount(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5805 const mod = func.bin_file.base.comp.module.?; 5806 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5807 5808 const operand = try func.resolveInst(ty_op.operand); 5809 const op_ty = func.typeOf(ty_op.operand); 5810 const result_ty = func.typeOfIndex(inst); 5811 5812 if (op_ty.zigTypeTag(mod) == .Vector) { 5813 return func.fail("TODO: Implement @popCount for vectors", .{}); 5814 } 5815 5816 const int_info = op_ty.intInfo(mod); 5817 const bits = int_info.bits; 5818 const wasm_bits = toWasmBits(bits) orelse { 5819 return func.fail("TODO: Implement @popCount for integers with bitsize '{d}'", .{bits}); 5820 }; 5821 5822 switch (wasm_bits) { 5823 128 => { 5824 _ = try func.load(operand, Type.u64, 0); 5825 try func.addTag(.i64_popcnt); 5826 _ = try func.load(operand, Type.u64, 8); 5827 try func.addTag(.i64_popcnt); 5828 try func.addTag(.i64_add); 5829 try func.addTag(.i32_wrap_i64); 5830 }, 5831 else => { 5832 try func.emitWValue(operand); 5833 switch (wasm_bits) { 5834 32 => try func.addTag(.i32_popcnt), 5835 64 => { 5836 try func.addTag(.i64_popcnt); 5837 try func.addTag(.i32_wrap_i64); 5838 }, 5839 else => unreachable, 5840 } 5841 }, 5842 } 5843 5844 const result = try func.allocLocal(result_ty); 5845 try func.addLabel(.local_set, result.local.value); 5846 func.finishAir(inst, result, &.{ty_op.operand}); 5847 } 5848 5849 fn airErrorName(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 5850 const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 5851 5852 const operand = try func.resolveInst(un_op); 5853 // First retrieve the symbol index to the error name table 5854 // that will be used to emit a relocation for the pointer 5855 // to the error name table. 5856 // 5857 // Each entry to this table is a slice (ptr+len). 5858 // The operand in this instruction represents the index within this table. 5859 // This means to get the final name, we emit the base pointer and then perform 5860 // pointer arithmetic to find the pointer to this slice and return that. 5861 // 5862 // As the names are global and the slice elements are constant, we do not have 5863 // to make a copy of the ptr+value but can point towards them directly. 5864 const error_table_symbol = try func.bin_file.getErrorTableSymbol(); 5865 const name_ty = Type.slice_const_u8_sentinel_0; 5866 const mod = func.bin_file.base.comp.module.?; 5867 const abi_size = name_ty.abiSize(mod); 5868 5869 const error_name_value: WValue = .{ .memory = error_table_symbol }; // emitting this will create a relocation 5870 try func.emitWValue(error_name_value); 5871 try func.emitWValue(operand); 5872 switch (func.arch()) { 5873 .wasm32 => { 5874 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(abi_size))))); 5875 try func.addTag(.i32_mul); 5876 try func.addTag(.i32_add); 5877 }, 5878 .wasm64 => { 5879 try func.addImm64(abi_size); 5880 try func.addTag(.i64_mul); 5881 try func.addTag(.i64_add); 5882 }, 5883 else => unreachable, 5884 } 5885 5886 const result_ptr = try func.allocLocal(Type.usize); 5887 try func.addLabel(.local_set, result_ptr.local.value); 5888 func.finishAir(inst, result_ptr, &.{un_op}); 5889 } 5890 5891 fn airPtrSliceFieldPtr(func: *CodeGen, inst: Air.Inst.Index, offset: u32) InnerError!void { 5892 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5893 const slice_ptr = try func.resolveInst(ty_op.operand); 5894 const result = try func.buildPointerOffset(slice_ptr, offset, .new); 5895 func.finishAir(inst, result, &.{ty_op.operand}); 5896 } 5897 5898 fn airAddSubWithOverflow(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void { 5899 assert(op == .add or op == .sub); 5900 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 5901 const extra = func.air.extraData(Air.Bin, ty_pl.payload).data; 5902 5903 const lhs_op = try func.resolveInst(extra.lhs); 5904 const rhs_op = try func.resolveInst(extra.rhs); 5905 const lhs_ty = func.typeOf(extra.lhs); 5906 const mod = func.bin_file.base.comp.module.?; 5907 5908 if (lhs_ty.zigTypeTag(mod) == .Vector) { 5909 return func.fail("TODO: Implement overflow arithmetic for vectors", .{}); 5910 } 5911 5912 const int_info = lhs_ty.intInfo(mod); 5913 const is_signed = int_info.signedness == .signed; 5914 const wasm_bits = toWasmBits(int_info.bits) orelse { 5915 return func.fail("TODO: Implement {{add/sub}}_with_overflow for integer bitsize: {d}", .{int_info.bits}); 5916 }; 5917 5918 if (wasm_bits == 128) { 5919 const result = try func.addSubWithOverflowBigInt(lhs_op, rhs_op, lhs_ty, func.typeOfIndex(inst), op); 5920 return func.finishAir(inst, result, &.{ extra.lhs, extra.rhs }); 5921 } 5922 5923 const zero = switch (wasm_bits) { 5924 32 => WValue{ .imm32 = 0 }, 5925 64 => WValue{ .imm64 = 0 }, 5926 else => unreachable, 5927 }; 5928 5929 // for signed integers, we first apply signed shifts by the difference in bits 5930 // to get the signed value, as we store it internally as 2's complement. 5931 var lhs = if (wasm_bits != int_info.bits and is_signed) blk: { 5932 break :blk try (try func.signExtendInt(lhs_op, lhs_ty)).toLocal(func, lhs_ty); 5933 } else lhs_op; 5934 var rhs = if (wasm_bits != int_info.bits and is_signed) blk: { 5935 break :blk try (try func.signExtendInt(rhs_op, lhs_ty)).toLocal(func, lhs_ty); 5936 } else rhs_op; 5937 5938 // in this case, we performed a signExtendInt which created a temporary local 5939 // so let's free this so it can be re-used instead. 5940 // In the other case we do not want to free it, because that would free the 5941 // resolved instructions which may be referenced by other instructions. 5942 defer if (wasm_bits != int_info.bits and is_signed) { 5943 lhs.free(func); 5944 rhs.free(func); 5945 }; 5946 5947 const bin_op = try (try func.binOp(lhs, rhs, lhs_ty, op)).toLocal(func, lhs_ty); 5948 var result = if (wasm_bits != int_info.bits) blk: { 5949 break :blk try (try func.wrapOperand(bin_op, lhs_ty)).toLocal(func, lhs_ty); 5950 } else bin_op; 5951 defer result.free(func); 5952 5953 const cmp_op: std.math.CompareOperator = if (op == .sub) .gt else .lt; 5954 const overflow_bit: WValue = if (is_signed) blk: { 5955 if (wasm_bits == int_info.bits) { 5956 const cmp_zero = try func.cmp(rhs, zero, lhs_ty, cmp_op); 5957 const lt = try func.cmp(bin_op, lhs, lhs_ty, .lt); 5958 break :blk try func.binOp(cmp_zero, lt, Type.u32, .xor); 5959 } 5960 const abs = try func.signExtendInt(bin_op, lhs_ty); 5961 break :blk try func.cmp(abs, bin_op, lhs_ty, .neq); 5962 } else if (wasm_bits == int_info.bits) 5963 try func.cmp(bin_op, lhs, lhs_ty, cmp_op) 5964 else 5965 try func.cmp(bin_op, result, lhs_ty, .neq); 5966 var overflow_local = try overflow_bit.toLocal(func, Type.u32); 5967 defer overflow_local.free(func); 5968 5969 const result_ptr = try func.allocStack(func.typeOfIndex(inst)); 5970 try func.store(result_ptr, result, lhs_ty, 0); 5971 const offset = @as(u32, @intCast(lhs_ty.abiSize(mod))); 5972 try func.store(result_ptr, overflow_local, Type.u1, offset); 5973 5974 func.finishAir(inst, result_ptr, &.{ extra.lhs, extra.rhs }); 5975 } 5976 5977 fn addSubWithOverflowBigInt(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, result_ty: Type, op: Op) InnerError!WValue { 5978 const mod = func.bin_file.base.comp.module.?; 5979 assert(op == .add or op == .sub); 5980 const int_info = ty.intInfo(mod); 5981 const is_signed = int_info.signedness == .signed; 5982 if (int_info.bits != 128) { 5983 return func.fail("TODO: Implement @{{add/sub}}WithOverflow for integer bitsize '{d}'", .{int_info.bits}); 5984 } 5985 5986 var lhs_high_bit = try (try func.load(lhs, Type.u64, 0)).toLocal(func, Type.u64); 5987 defer lhs_high_bit.free(func); 5988 var lhs_low_bit = try (try func.load(lhs, Type.u64, 8)).toLocal(func, Type.u64); 5989 defer lhs_low_bit.free(func); 5990 var rhs_high_bit = try (try func.load(rhs, Type.u64, 0)).toLocal(func, Type.u64); 5991 defer rhs_high_bit.free(func); 5992 var rhs_low_bit = try (try func.load(rhs, Type.u64, 8)).toLocal(func, Type.u64); 5993 defer rhs_low_bit.free(func); 5994 5995 var low_op_res = try (try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, op)).toLocal(func, Type.u64); 5996 defer low_op_res.free(func); 5997 var high_op_res = try (try func.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op)).toLocal(func, Type.u64); 5998 defer high_op_res.free(func); 5999 6000 var lt = if (op == .add) blk: { 6001 break :blk try (try func.cmp(high_op_res, lhs_high_bit, Type.u64, .lt)).toLocal(func, Type.u32); 6002 } else if (op == .sub) blk: { 6003 break :blk try (try func.cmp(lhs_high_bit, rhs_high_bit, Type.u64, .lt)).toLocal(func, Type.u32); 6004 } else unreachable; 6005 defer lt.free(func); 6006 var tmp = try (try func.intcast(lt, Type.u32, Type.u64)).toLocal(func, Type.u64); 6007 defer tmp.free(func); 6008 var tmp_op = try (try func.binOp(low_op_res, tmp, Type.u64, op)).toLocal(func, Type.u64); 6009 defer tmp_op.free(func); 6010 6011 const overflow_bit = if (is_signed) blk: { 6012 const xor_low = try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, .xor); 6013 const to_wrap = if (op == .add) wrap: { 6014 break :wrap try func.binOp(xor_low, .{ .imm64 = ~@as(u64, 0) }, Type.u64, .xor); 6015 } else xor_low; 6016 const xor_op = try func.binOp(lhs_low_bit, tmp_op, Type.u64, .xor); 6017 const wrap = try func.binOp(to_wrap, xor_op, Type.u64, .@"and"); 6018 break :blk try func.cmp(wrap, .{ .imm64 = 0 }, Type.i64, .lt); // i64 because signed 6019 } else blk: { 6020 const first_arg = if (op == .sub) arg: { 6021 break :arg try func.cmp(high_op_res, lhs_high_bit, Type.u64, .gt); 6022 } else lt; 6023 6024 try func.emitWValue(first_arg); 6025 _ = try func.cmp(tmp_op, lhs_low_bit, Type.u64, if (op == .add) .lt else .gt); 6026 _ = try func.cmp(tmp_op, lhs_low_bit, Type.u64, .eq); 6027 try func.addTag(.select); 6028 6029 break :blk WValue{ .stack = {} }; 6030 }; 6031 var overflow_local = try overflow_bit.toLocal(func, Type.u1); 6032 defer overflow_local.free(func); 6033 6034 const result_ptr = try func.allocStack(result_ty); 6035 try func.store(result_ptr, high_op_res, Type.u64, 0); 6036 try func.store(result_ptr, tmp_op, Type.u64, 8); 6037 try func.store(result_ptr, overflow_local, Type.u1, 16); 6038 6039 return result_ptr; 6040 } 6041 6042 fn airShlWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 6043 const mod = func.bin_file.base.comp.module.?; 6044 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 6045 const extra = func.air.extraData(Air.Bin, ty_pl.payload).data; 6046 6047 const lhs = try func.resolveInst(extra.lhs); 6048 const rhs = try func.resolveInst(extra.rhs); 6049 const lhs_ty = func.typeOf(extra.lhs); 6050 const rhs_ty = func.typeOf(extra.rhs); 6051 6052 if (lhs_ty.zigTypeTag(mod) == .Vector) { 6053 return func.fail("TODO: Implement overflow arithmetic for vectors", .{}); 6054 } 6055 6056 const int_info = lhs_ty.intInfo(mod); 6057 const is_signed = int_info.signedness == .signed; 6058 const wasm_bits = toWasmBits(int_info.bits) orelse { 6059 return func.fail("TODO: Implement shl_with_overflow for integer bitsize: {d}", .{int_info.bits}); 6060 }; 6061 6062 // Ensure rhs is coerced to lhs as they must have the same WebAssembly types 6063 // before we can perform any binary operation. 6064 const rhs_wasm_bits = toWasmBits(rhs_ty.intInfo(mod).bits).?; 6065 const rhs_final = if (wasm_bits != rhs_wasm_bits) blk: { 6066 const rhs_casted = try func.intcast(rhs, rhs_ty, lhs_ty); 6067 break :blk try rhs_casted.toLocal(func, lhs_ty); 6068 } else rhs; 6069 6070 var shl = try (try func.binOp(lhs, rhs_final, lhs_ty, .shl)).toLocal(func, lhs_ty); 6071 defer shl.free(func); 6072 var result = if (wasm_bits != int_info.bits) blk: { 6073 break :blk try (try func.wrapOperand(shl, lhs_ty)).toLocal(func, lhs_ty); 6074 } else shl; 6075 defer result.free(func); // it's a no-op to free the same local twice (when wasm_bits == int_info.bits) 6076 6077 const overflow_bit = if (wasm_bits != int_info.bits and is_signed) blk: { 6078 // emit lhs to stack to we can keep 'wrapped' on the stack also 6079 try func.emitWValue(lhs); 6080 const abs = try func.signExtendInt(shl, lhs_ty); 6081 const wrapped = try func.wrapBinOp(abs, rhs_final, lhs_ty, .shr); 6082 break :blk try func.cmp(.{ .stack = {} }, wrapped, lhs_ty, .neq); 6083 } else blk: { 6084 try func.emitWValue(lhs); 6085 const shr = try func.binOp(result, rhs_final, lhs_ty, .shr); 6086 break :blk try func.cmp(.{ .stack = {} }, shr, lhs_ty, .neq); 6087 }; 6088 var overflow_local = try overflow_bit.toLocal(func, Type.u1); 6089 defer overflow_local.free(func); 6090 6091 const result_ptr = try func.allocStack(func.typeOfIndex(inst)); 6092 try func.store(result_ptr, result, lhs_ty, 0); 6093 const offset = @as(u32, @intCast(lhs_ty.abiSize(mod))); 6094 try func.store(result_ptr, overflow_local, Type.u1, offset); 6095 6096 func.finishAir(inst, result_ptr, &.{ extra.lhs, extra.rhs }); 6097 } 6098 6099 fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 6100 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 6101 const extra = func.air.extraData(Air.Bin, ty_pl.payload).data; 6102 6103 const lhs = try func.resolveInst(extra.lhs); 6104 const rhs = try func.resolveInst(extra.rhs); 6105 const lhs_ty = func.typeOf(extra.lhs); 6106 const mod = func.bin_file.base.comp.module.?; 6107 6108 if (lhs_ty.zigTypeTag(mod) == .Vector) { 6109 return func.fail("TODO: Implement overflow arithmetic for vectors", .{}); 6110 } 6111 6112 // We store the bit if it's overflowed or not in this. As it's zero-initialized 6113 // we only need to update it if an overflow (or underflow) occurred. 6114 var overflow_bit = try func.ensureAllocLocal(Type.u1); 6115 defer overflow_bit.free(func); 6116 6117 const int_info = lhs_ty.intInfo(mod); 6118 const wasm_bits = toWasmBits(int_info.bits) orelse { 6119 return func.fail("TODO: Implement `@mulWithOverflow` for integer bitsize: {d}", .{int_info.bits}); 6120 }; 6121 6122 const zero = switch (wasm_bits) { 6123 32 => WValue{ .imm32 = 0 }, 6124 64, 128 => WValue{ .imm64 = 0 }, 6125 else => unreachable, 6126 }; 6127 6128 // for 32 bit integers we upcast it to a 64bit integer 6129 const bin_op = if (int_info.bits == 32) blk: { 6130 const new_ty = if (int_info.signedness == .signed) Type.i64 else Type.u64; 6131 const lhs_upcast = try func.intcast(lhs, lhs_ty, new_ty); 6132 const rhs_upcast = try func.intcast(rhs, lhs_ty, new_ty); 6133 const bin_op = try (try func.binOp(lhs_upcast, rhs_upcast, new_ty, .mul)).toLocal(func, new_ty); 6134 if (int_info.signedness == .unsigned) { 6135 const shr = try func.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr); 6136 const wrap = try func.intcast(shr, new_ty, lhs_ty); 6137 _ = try func.cmp(wrap, zero, lhs_ty, .neq); 6138 try func.addLabel(.local_set, overflow_bit.local.value); 6139 break :blk try func.intcast(bin_op, new_ty, lhs_ty); 6140 } else { 6141 const down_cast = try (try func.intcast(bin_op, new_ty, lhs_ty)).toLocal(func, lhs_ty); 6142 var shr = try (try func.binOp(down_cast, .{ .imm32 = int_info.bits - 1 }, lhs_ty, .shr)).toLocal(func, lhs_ty); 6143 defer shr.free(func); 6144 6145 const shr_res = try func.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr); 6146 const down_shr_res = try func.intcast(shr_res, new_ty, lhs_ty); 6147 _ = try func.cmp(down_shr_res, shr, lhs_ty, .neq); 6148 try func.addLabel(.local_set, overflow_bit.local.value); 6149 break :blk down_cast; 6150 } 6151 } else if (int_info.signedness == .signed and wasm_bits == 32) blk: { 6152 const lhs_abs = try func.signExtendInt(lhs, lhs_ty); 6153 const rhs_abs = try func.signExtendInt(rhs, lhs_ty); 6154 const bin_op = try (try func.binOp(lhs_abs, rhs_abs, lhs_ty, .mul)).toLocal(func, lhs_ty); 6155 const mul_abs = try func.signExtendInt(bin_op, lhs_ty); 6156 _ = try func.cmp(mul_abs, bin_op, lhs_ty, .neq); 6157 try func.addLabel(.local_set, overflow_bit.local.value); 6158 break :blk try func.wrapOperand(bin_op, lhs_ty); 6159 } else if (wasm_bits == 32) blk: { 6160 var bin_op = try (try func.binOp(lhs, rhs, lhs_ty, .mul)).toLocal(func, lhs_ty); 6161 defer bin_op.free(func); 6162 const shift_imm = if (wasm_bits == 32) 6163 WValue{ .imm32 = int_info.bits } 6164 else 6165 WValue{ .imm64 = int_info.bits }; 6166 const shr = try func.binOp(bin_op, shift_imm, lhs_ty, .shr); 6167 _ = try func.cmp(shr, zero, lhs_ty, .neq); 6168 try func.addLabel(.local_set, overflow_bit.local.value); 6169 break :blk try func.wrapOperand(bin_op, lhs_ty); 6170 } else if (int_info.bits == 64 and int_info.signedness == .unsigned) blk: { 6171 const new_ty = Type.u128; 6172 var lhs_upcast = try (try func.intcast(lhs, lhs_ty, new_ty)).toLocal(func, lhs_ty); 6173 defer lhs_upcast.free(func); 6174 var rhs_upcast = try (try func.intcast(rhs, lhs_ty, new_ty)).toLocal(func, lhs_ty); 6175 defer rhs_upcast.free(func); 6176 const bin_op = try func.binOp(lhs_upcast, rhs_upcast, new_ty, .mul); 6177 const lsb = try func.load(bin_op, lhs_ty, 8); 6178 _ = try func.cmp(lsb, zero, lhs_ty, .neq); 6179 try func.addLabel(.local_set, overflow_bit.local.value); 6180 6181 break :blk try func.load(bin_op, lhs_ty, 0); 6182 } else if (int_info.bits == 64 and int_info.signedness == .signed) blk: { 6183 const shift_val: WValue = .{ .imm64 = 63 }; 6184 var lhs_shifted = try (try func.binOp(lhs, shift_val, lhs_ty, .shr)).toLocal(func, lhs_ty); 6185 defer lhs_shifted.free(func); 6186 var rhs_shifted = try (try func.binOp(rhs, shift_val, lhs_ty, .shr)).toLocal(func, lhs_ty); 6187 defer rhs_shifted.free(func); 6188 6189 const bin_op = try func.callIntrinsic( 6190 "__multi3", 6191 &[_]InternPool.Index{.i64_type} ** 4, 6192 Type.i128, 6193 &.{ lhs, lhs_shifted, rhs, rhs_shifted }, 6194 ); 6195 const res = try func.allocLocal(lhs_ty); 6196 const msb = try func.load(bin_op, lhs_ty, 0); 6197 try func.addLabel(.local_tee, res.local.value); 6198 const msb_shifted = try func.binOp(msb, shift_val, lhs_ty, .shr); 6199 const lsb = try func.load(bin_op, lhs_ty, 8); 6200 _ = try func.cmp(lsb, msb_shifted, lhs_ty, .neq); 6201 try func.addLabel(.local_set, overflow_bit.local.value); 6202 break :blk res; 6203 } else if (int_info.bits == 128 and int_info.signedness == .unsigned) blk: { 6204 var lhs_msb = try (try func.load(lhs, Type.u64, 0)).toLocal(func, Type.u64); 6205 defer lhs_msb.free(func); 6206 var lhs_lsb = try (try func.load(lhs, Type.u64, 8)).toLocal(func, Type.u64); 6207 defer lhs_lsb.free(func); 6208 var rhs_msb = try (try func.load(rhs, Type.u64, 0)).toLocal(func, Type.u64); 6209 defer rhs_msb.free(func); 6210 var rhs_lsb = try (try func.load(rhs, Type.u64, 8)).toLocal(func, Type.u64); 6211 defer rhs_lsb.free(func); 6212 6213 const mul1 = try func.callIntrinsic( 6214 "__multi3", 6215 &[_]InternPool.Index{.i64_type} ** 4, 6216 Type.i128, 6217 &.{ lhs_lsb, zero, rhs_msb, zero }, 6218 ); 6219 const mul2 = try func.callIntrinsic( 6220 "__multi3", 6221 &[_]InternPool.Index{.i64_type} ** 4, 6222 Type.i128, 6223 &.{ rhs_lsb, zero, lhs_msb, zero }, 6224 ); 6225 const mul3 = try func.callIntrinsic( 6226 "__multi3", 6227 &[_]InternPool.Index{.i64_type} ** 4, 6228 Type.i128, 6229 &.{ lhs_msb, zero, rhs_msb, zero }, 6230 ); 6231 6232 const rhs_lsb_not_zero = try func.cmp(rhs_lsb, zero, Type.u64, .neq); 6233 const lhs_lsb_not_zero = try func.cmp(lhs_lsb, zero, Type.u64, .neq); 6234 const lsb_and = try func.binOp(rhs_lsb_not_zero, lhs_lsb_not_zero, Type.bool, .@"and"); 6235 const mul1_lsb = try func.load(mul1, Type.u64, 8); 6236 const mul1_lsb_not_zero = try func.cmp(mul1_lsb, zero, Type.u64, .neq); 6237 const lsb_or1 = try func.binOp(lsb_and, mul1_lsb_not_zero, Type.bool, .@"or"); 6238 const mul2_lsb = try func.load(mul2, Type.u64, 8); 6239 const mul2_lsb_not_zero = try func.cmp(mul2_lsb, zero, Type.u64, .neq); 6240 const lsb_or = try func.binOp(lsb_or1, mul2_lsb_not_zero, Type.bool, .@"or"); 6241 6242 const mul1_msb = try func.load(mul1, Type.u64, 0); 6243 const mul2_msb = try func.load(mul2, Type.u64, 0); 6244 const mul_add1 = try func.binOp(mul1_msb, mul2_msb, Type.u64, .add); 6245 6246 var mul3_lsb = try (try func.load(mul3, Type.u64, 8)).toLocal(func, Type.u64); 6247 defer mul3_lsb.free(func); 6248 var mul_add2 = try (try func.binOp(mul_add1, mul3_lsb, Type.u64, .add)).toLocal(func, Type.u64); 6249 defer mul_add2.free(func); 6250 const mul_add_lt = try func.cmp(mul_add2, mul3_lsb, Type.u64, .lt); 6251 6252 // result for overflow bit 6253 _ = try func.binOp(lsb_or, mul_add_lt, Type.bool, .@"or"); 6254 try func.addLabel(.local_set, overflow_bit.local.value); 6255 6256 const tmp_result = try func.allocStack(Type.u128); 6257 try func.emitWValue(tmp_result); 6258 const mul3_msb = try func.load(mul3, Type.u64, 0); 6259 try func.store(.stack, mul3_msb, Type.u64, tmp_result.offset()); 6260 try func.store(tmp_result, mul_add2, Type.u64, 8); 6261 break :blk tmp_result; 6262 } else return func.fail("TODO: @mulWithOverflow for integers between 32 and 64 bits", .{}); 6263 var bin_op_local = try bin_op.toLocal(func, lhs_ty); 6264 defer bin_op_local.free(func); 6265 6266 const result_ptr = try func.allocStack(func.typeOfIndex(inst)); 6267 try func.store(result_ptr, bin_op_local, lhs_ty, 0); 6268 const offset = @as(u32, @intCast(lhs_ty.abiSize(mod))); 6269 try func.store(result_ptr, overflow_bit, Type.u1, offset); 6270 6271 func.finishAir(inst, result_ptr, &.{ extra.lhs, extra.rhs }); 6272 } 6273 6274 fn airMaxMin(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void { 6275 assert(op == .max or op == .min); 6276 const mod = func.bin_file.base.comp.module.?; 6277 const target = mod.getTarget(); 6278 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 6279 6280 const ty = func.typeOfIndex(inst); 6281 if (ty.zigTypeTag(mod) == .Vector) { 6282 return func.fail("TODO: `@maximum` and `@minimum` for vectors", .{}); 6283 } 6284 6285 if (ty.abiSize(mod) > 16) { 6286 return func.fail("TODO: `@maximum` and `@minimum` for types larger than 16 bytes", .{}); 6287 } 6288 6289 const lhs = try func.resolveInst(bin_op.lhs); 6290 const rhs = try func.resolveInst(bin_op.rhs); 6291 6292 if (ty.zigTypeTag(mod) == .Float) { 6293 var fn_name_buf: [64]u8 = undefined; 6294 const float_bits = ty.floatBits(target); 6295 const fn_name = std.fmt.bufPrint(&fn_name_buf, "{s}f{s}{s}", .{ 6296 target_util.libcFloatPrefix(float_bits), 6297 @tagName(op), 6298 target_util.libcFloatSuffix(float_bits), 6299 }) catch unreachable; 6300 const result = try func.callIntrinsic(fn_name, &.{ ty.ip_index, ty.ip_index }, ty, &.{ lhs, rhs }); 6301 try func.lowerToStack(result); 6302 } else { 6303 // operands to select from 6304 try func.lowerToStack(lhs); 6305 try func.lowerToStack(rhs); 6306 _ = try func.cmp(lhs, rhs, ty, if (op == .max) .gt else .lt); 6307 6308 // based on the result from comparison, return operand 0 or 1. 6309 try func.addTag(.select); 6310 } 6311 6312 // store result in local 6313 const result_ty = if (isByRef(ty, mod)) Type.u32 else ty; 6314 const result = try func.allocLocal(result_ty); 6315 try func.addLabel(.local_set, result.local.value); 6316 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 6317 } 6318 6319 fn airMulAdd(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 6320 const mod = func.bin_file.base.comp.module.?; 6321 const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 6322 const bin_op = func.air.extraData(Air.Bin, pl_op.payload).data; 6323 6324 const ty = func.typeOfIndex(inst); 6325 if (ty.zigTypeTag(mod) == .Vector) { 6326 return func.fail("TODO: `@mulAdd` for vectors", .{}); 6327 } 6328 6329 const addend = try func.resolveInst(pl_op.operand); 6330 const lhs = try func.resolveInst(bin_op.lhs); 6331 const rhs = try func.resolveInst(bin_op.rhs); 6332 6333 const result = if (ty.floatBits(func.target) == 16) fl_result: { 6334 const rhs_ext = try func.fpext(rhs, ty, Type.f32); 6335 const lhs_ext = try func.fpext(lhs, ty, Type.f32); 6336 const addend_ext = try func.fpext(addend, ty, Type.f32); 6337 // call to compiler-rt `fn fmaf(f32, f32, f32) f32` 6338 const result = try func.callIntrinsic( 6339 "fmaf", 6340 &.{ .f32_type, .f32_type, .f32_type }, 6341 Type.f32, 6342 &.{ rhs_ext, lhs_ext, addend_ext }, 6343 ); 6344 break :fl_result try (try func.fptrunc(result, Type.f32, ty)).toLocal(func, ty); 6345 } else result: { 6346 const mul_result = try func.binOp(lhs, rhs, ty, .mul); 6347 break :result try (try func.binOp(mul_result, addend, ty, .add)).toLocal(func, ty); 6348 }; 6349 6350 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs, pl_op.operand }); 6351 } 6352 6353 fn airClz(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 6354 const mod = func.bin_file.base.comp.module.?; 6355 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 6356 6357 const ty = func.typeOf(ty_op.operand); 6358 const result_ty = func.typeOfIndex(inst); 6359 if (ty.zigTypeTag(mod) == .Vector) { 6360 return func.fail("TODO: `@clz` for vectors", .{}); 6361 } 6362 6363 const operand = try func.resolveInst(ty_op.operand); 6364 const int_info = ty.intInfo(mod); 6365 const wasm_bits = toWasmBits(int_info.bits) orelse { 6366 return func.fail("TODO: `@clz` for integers with bitsize '{d}'", .{int_info.bits}); 6367 }; 6368 6369 switch (wasm_bits) { 6370 32 => { 6371 try func.emitWValue(operand); 6372 try func.addTag(.i32_clz); 6373 }, 6374 64 => { 6375 try func.emitWValue(operand); 6376 try func.addTag(.i64_clz); 6377 try func.addTag(.i32_wrap_i64); 6378 }, 6379 128 => { 6380 var lsb = try (try func.load(operand, Type.u64, 8)).toLocal(func, Type.u64); 6381 defer lsb.free(func); 6382 6383 try func.emitWValue(lsb); 6384 try func.addTag(.i64_clz); 6385 _ = try func.load(operand, Type.u64, 0); 6386 try func.addTag(.i64_clz); 6387 try func.emitWValue(.{ .imm64 = 64 }); 6388 try func.addTag(.i64_add); 6389 _ = try func.cmp(lsb, .{ .imm64 = 0 }, Type.u64, .neq); 6390 try func.addTag(.select); 6391 try func.addTag(.i32_wrap_i64); 6392 }, 6393 else => unreachable, 6394 } 6395 6396 if (wasm_bits != int_info.bits) { 6397 try func.emitWValue(.{ .imm32 = wasm_bits - int_info.bits }); 6398 try func.addTag(.i32_sub); 6399 } 6400 6401 const result = try func.allocLocal(result_ty); 6402 try func.addLabel(.local_set, result.local.value); 6403 func.finishAir(inst, result, &.{ty_op.operand}); 6404 } 6405 6406 fn airCtz(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 6407 const mod = func.bin_file.base.comp.module.?; 6408 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 6409 6410 const ty = func.typeOf(ty_op.operand); 6411 const result_ty = func.typeOfIndex(inst); 6412 6413 if (ty.zigTypeTag(mod) == .Vector) { 6414 return func.fail("TODO: `@ctz` for vectors", .{}); 6415 } 6416 6417 const operand = try func.resolveInst(ty_op.operand); 6418 const int_info = ty.intInfo(mod); 6419 const wasm_bits = toWasmBits(int_info.bits) orelse { 6420 return func.fail("TODO: `@clz` for integers with bitsize '{d}'", .{int_info.bits}); 6421 }; 6422 6423 switch (wasm_bits) { 6424 32 => { 6425 if (wasm_bits != int_info.bits) { 6426 const val: u32 = @as(u32, 1) << @as(u5, @intCast(int_info.bits)); 6427 // leave value on the stack 6428 _ = try func.binOp(operand, .{ .imm32 = val }, ty, .@"or"); 6429 } else try func.emitWValue(operand); 6430 try func.addTag(.i32_ctz); 6431 }, 6432 64 => { 6433 if (wasm_bits != int_info.bits) { 6434 const val: u64 = @as(u64, 1) << @as(u6, @intCast(int_info.bits)); 6435 // leave value on the stack 6436 _ = try func.binOp(operand, .{ .imm64 = val }, ty, .@"or"); 6437 } else try func.emitWValue(operand); 6438 try func.addTag(.i64_ctz); 6439 try func.addTag(.i32_wrap_i64); 6440 }, 6441 128 => { 6442 var msb = try (try func.load(operand, Type.u64, 0)).toLocal(func, Type.u64); 6443 defer msb.free(func); 6444 6445 try func.emitWValue(msb); 6446 try func.addTag(.i64_ctz); 6447 _ = try func.load(operand, Type.u64, 8); 6448 if (wasm_bits != int_info.bits) { 6449 try func.addImm64(@as(u64, 1) << @as(u6, @intCast(int_info.bits - 64))); 6450 try func.addTag(.i64_or); 6451 } 6452 try func.addTag(.i64_ctz); 6453 try func.addImm64(64); 6454 if (wasm_bits != int_info.bits) { 6455 try func.addTag(.i64_or); 6456 } else { 6457 try func.addTag(.i64_add); 6458 } 6459 _ = try func.cmp(msb, .{ .imm64 = 0 }, Type.u64, .neq); 6460 try func.addTag(.select); 6461 try func.addTag(.i32_wrap_i64); 6462 }, 6463 else => unreachable, 6464 } 6465 6466 const result = try func.allocLocal(result_ty); 6467 try func.addLabel(.local_set, result.local.value); 6468 func.finishAir(inst, result, &.{ty_op.operand}); 6469 } 6470 6471 fn airDbgVar(func: *CodeGen, inst: Air.Inst.Index, is_ptr: bool) !void { 6472 if (func.debug_output != .dwarf) return func.finishAir(inst, .none, &.{}); 6473 6474 const mod = func.bin_file.base.comp.module.?; 6475 const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 6476 const ty = func.typeOf(pl_op.operand); 6477 const operand = try func.resolveInst(pl_op.operand); 6478 6479 log.debug("airDbgVar: %{d}: {}, {}", .{ inst, ty.fmtDebug(), operand }); 6480 6481 const name = func.air.nullTerminatedString(pl_op.payload); 6482 log.debug(" var name = ({s})", .{name}); 6483 6484 const loc: link.File.Dwarf.DeclState.DbgInfoLoc = switch (operand) { 6485 .local => |local| .{ .wasm_local = local.value }, 6486 else => blk: { 6487 log.debug("TODO generate debug info for {}", .{operand}); 6488 break :blk .nop; 6489 }, 6490 }; 6491 try func.debug_output.dwarf.genVarDbgInfo(name, ty, mod.funcOwnerDeclIndex(func.func_index), is_ptr, loc); 6492 6493 func.finishAir(inst, .none, &.{}); 6494 } 6495 6496 fn airDbgStmt(func: *CodeGen, inst: Air.Inst.Index) !void { 6497 if (func.debug_output != .dwarf) return func.finishAir(inst, .none, &.{}); 6498 6499 const dbg_stmt = func.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt; 6500 try func.addInst(.{ .tag = .dbg_line, .data = .{ 6501 .payload = try func.addExtra(Mir.DbgLineColumn{ 6502 .line = dbg_stmt.line, 6503 .column = dbg_stmt.column, 6504 }), 6505 } }); 6506 func.finishAir(inst, .none, &.{}); 6507 } 6508 6509 fn airTry(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 6510 const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 6511 const err_union = try func.resolveInst(pl_op.operand); 6512 const extra = func.air.extraData(Air.Try, pl_op.payload); 6513 const body: []const Air.Inst.Index = @ptrCast(func.air.extra[extra.end..][0..extra.data.body_len]); 6514 const err_union_ty = func.typeOf(pl_op.operand); 6515 const result = try lowerTry(func, inst, err_union, body, err_union_ty, false); 6516 func.finishAir(inst, result, &.{pl_op.operand}); 6517 } 6518 6519 fn airTryPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 6520 const mod = func.bin_file.base.comp.module.?; 6521 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 6522 const extra = func.air.extraData(Air.TryPtr, ty_pl.payload); 6523 const err_union_ptr = try func.resolveInst(extra.data.ptr); 6524 const body: []const Air.Inst.Index = @ptrCast(func.air.extra[extra.end..][0..extra.data.body_len]); 6525 const err_union_ty = func.typeOf(extra.data.ptr).childType(mod); 6526 const result = try lowerTry(func, inst, err_union_ptr, body, err_union_ty, true); 6527 func.finishAir(inst, result, &.{extra.data.ptr}); 6528 } 6529 6530 fn lowerTry( 6531 func: *CodeGen, 6532 inst: Air.Inst.Index, 6533 err_union: WValue, 6534 body: []const Air.Inst.Index, 6535 err_union_ty: Type, 6536 operand_is_ptr: bool, 6537 ) InnerError!WValue { 6538 const mod = func.bin_file.base.comp.module.?; 6539 if (operand_is_ptr) { 6540 return func.fail("TODO: lowerTry for pointers", .{}); 6541 } 6542 6543 const pl_ty = err_union_ty.errorUnionPayload(mod); 6544 const pl_has_bits = pl_ty.hasRuntimeBitsIgnoreComptime(mod); 6545 6546 if (!err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) { 6547 // Block we can jump out of when error is not set 6548 try func.startBlock(.block, wasm.block_empty); 6549 6550 // check if the error tag is set for the error union. 6551 try func.emitWValue(err_union); 6552 if (pl_has_bits) { 6553 const err_offset = @as(u32, @intCast(errUnionErrorOffset(pl_ty, mod))); 6554 try func.addMemArg(.i32_load16_u, .{ 6555 .offset = err_union.offset() + err_offset, 6556 .alignment = @intCast(Type.anyerror.abiAlignment(mod).toByteUnitsOptional().?), 6557 }); 6558 } 6559 try func.addTag(.i32_eqz); 6560 try func.addLabel(.br_if, 0); // jump out of block when error is '0' 6561 6562 const liveness = func.liveness.getCondBr(inst); 6563 try func.branches.append(func.gpa, .{}); 6564 try func.currentBranch().values.ensureUnusedCapacity(func.gpa, liveness.else_deaths.len + liveness.then_deaths.len); 6565 defer { 6566 var branch = func.branches.pop(); 6567 branch.deinit(func.gpa); 6568 } 6569 try func.genBody(body); 6570 try func.endBlock(); 6571 } 6572 6573 // if we reach here it means error was not set, and we want the payload 6574 if (!pl_has_bits) { 6575 return WValue{ .none = {} }; 6576 } 6577 6578 const pl_offset = @as(u32, @intCast(errUnionPayloadOffset(pl_ty, mod))); 6579 if (isByRef(pl_ty, mod)) { 6580 return buildPointerOffset(func, err_union, pl_offset, .new); 6581 } 6582 const payload = try func.load(err_union, pl_ty, pl_offset); 6583 return payload.toLocal(func, pl_ty); 6584 } 6585 6586 fn airByteSwap(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 6587 const mod = func.bin_file.base.comp.module.?; 6588 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 6589 6590 const ty = func.typeOfIndex(inst); 6591 const operand = try func.resolveInst(ty_op.operand); 6592 6593 if (ty.zigTypeTag(mod) == .Vector) { 6594 return func.fail("TODO: @byteSwap for vectors", .{}); 6595 } 6596 const int_info = ty.intInfo(mod); 6597 6598 // bytes are no-op 6599 if (int_info.bits == 8) { 6600 return func.finishAir(inst, func.reuseOperand(ty_op.operand, operand), &.{ty_op.operand}); 6601 } 6602 6603 const result = result: { 6604 switch (int_info.bits) { 6605 16 => { 6606 const shl_res = try func.binOp(operand, .{ .imm32 = 8 }, ty, .shl); 6607 const lhs = try func.binOp(shl_res, .{ .imm32 = 0xFF00 }, ty, .@"and"); 6608 const shr_res = try func.binOp(operand, .{ .imm32 = 8 }, ty, .shr); 6609 const res = if (int_info.signedness == .signed) blk: { 6610 break :blk try func.wrapOperand(shr_res, Type.u8); 6611 } else shr_res; 6612 break :result try (try func.binOp(lhs, res, ty, .@"or")).toLocal(func, ty); 6613 }, 6614 24 => { 6615 var msb = try (try func.wrapOperand(operand, Type.u16)).toLocal(func, Type.u16); 6616 defer msb.free(func); 6617 6618 const shl_res = try func.binOp(msb, .{ .imm32 = 8 }, Type.u16, .shl); 6619 const lhs = try func.binOp(shl_res, .{ .imm32 = 0xFF0000 }, Type.u16, .@"and"); 6620 const shr_res = try func.binOp(msb, .{ .imm32 = 8 }, ty, .shr); 6621 6622 const res = if (int_info.signedness == .signed) blk: { 6623 break :blk try func.wrapOperand(shr_res, Type.u8); 6624 } else shr_res; 6625 const lhs_tmp = try func.binOp(lhs, res, ty, .@"or"); 6626 const lhs_result = try func.binOp(lhs_tmp, .{ .imm32 = 8 }, ty, .shr); 6627 const rhs_wrap = try func.wrapOperand(msb, Type.u8); 6628 const rhs_result = try func.binOp(rhs_wrap, .{ .imm32 = 16 }, ty, .shl); 6629 6630 const lsb = try func.wrapBinOp(operand, .{ .imm32 = 16 }, Type.u8, .shr); 6631 const tmp = try func.binOp(lhs_result, rhs_result, ty, .@"or"); 6632 break :result try (try func.binOp(tmp, lsb, ty, .@"or")).toLocal(func, ty); 6633 }, 6634 32 => { 6635 const shl_tmp = try func.binOp(operand, .{ .imm32 = 8 }, ty, .shl); 6636 var lhs = try (try func.binOp(shl_tmp, .{ .imm32 = 0xFF00FF00 }, ty, .@"and")).toLocal(func, ty); 6637 defer lhs.free(func); 6638 const shr_tmp = try func.binOp(operand, .{ .imm32 = 8 }, ty, .shr); 6639 var rhs = try (try func.binOp(shr_tmp, .{ .imm32 = 0xFF00FF }, ty, .@"and")).toLocal(func, ty); 6640 defer rhs.free(func); 6641 var tmp_or = try (try func.binOp(lhs, rhs, ty, .@"or")).toLocal(func, ty); 6642 defer tmp_or.free(func); 6643 6644 const shl = try func.binOp(tmp_or, .{ .imm32 = 16 }, ty, .shl); 6645 const shr = try func.binOp(tmp_or, .{ .imm32 = 16 }, ty, .shr); 6646 const res = if (int_info.signedness == .signed) blk: { 6647 break :blk try func.wrapOperand(shr, Type.u16); 6648 } else shr; 6649 break :result try (try func.binOp(shl, res, ty, .@"or")).toLocal(func, ty); 6650 }, 6651 else => return func.fail("TODO: @byteSwap for integers with bitsize {d}", .{int_info.bits}), 6652 } 6653 }; 6654 func.finishAir(inst, result, &.{ty_op.operand}); 6655 } 6656 6657 fn airDiv(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 6658 const mod = func.bin_file.base.comp.module.?; 6659 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 6660 6661 const ty = func.typeOfIndex(inst); 6662 const lhs = try func.resolveInst(bin_op.lhs); 6663 const rhs = try func.resolveInst(bin_op.rhs); 6664 6665 const result = if (ty.isSignedInt(mod)) 6666 try func.divSigned(lhs, rhs, ty) 6667 else 6668 try (try func.binOp(lhs, rhs, ty, .div)).toLocal(func, ty); 6669 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 6670 } 6671 6672 fn airDivTrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 6673 const mod = func.bin_file.base.comp.module.?; 6674 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 6675 6676 const ty = func.typeOfIndex(inst); 6677 const lhs = try func.resolveInst(bin_op.lhs); 6678 const rhs = try func.resolveInst(bin_op.rhs); 6679 6680 const div_result = if (ty.isSignedInt(mod)) 6681 try func.divSigned(lhs, rhs, ty) 6682 else 6683 try (try func.binOp(lhs, rhs, ty, .div)).toLocal(func, ty); 6684 6685 if (ty.isAnyFloat()) { 6686 const trunc_result = try (try func.floatOp(.trunc, ty, &.{div_result})).toLocal(func, ty); 6687 return func.finishAir(inst, trunc_result, &.{ bin_op.lhs, bin_op.rhs }); 6688 } 6689 6690 return func.finishAir(inst, div_result, &.{ bin_op.lhs, bin_op.rhs }); 6691 } 6692 6693 fn airDivFloor(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 6694 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 6695 6696 const mod = func.bin_file.base.comp.module.?; 6697 const ty = func.typeOfIndex(inst); 6698 const lhs = try func.resolveInst(bin_op.lhs); 6699 const rhs = try func.resolveInst(bin_op.rhs); 6700 6701 if (ty.isUnsignedInt(mod)) { 6702 _ = try func.binOp(lhs, rhs, ty, .div); 6703 } else if (ty.isSignedInt(mod)) { 6704 const int_bits = ty.intInfo(mod).bits; 6705 const wasm_bits = toWasmBits(int_bits) orelse { 6706 return func.fail("TODO: `@divFloor` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits}); 6707 }; 6708 6709 if (wasm_bits > 64) { 6710 return func.fail("TODO: `@divFloor` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits}); 6711 } 6712 6713 const lhs_wasm = if (wasm_bits != int_bits) 6714 try (try func.signExtendInt(lhs, ty)).toLocal(func, ty) 6715 else 6716 lhs; 6717 6718 const rhs_wasm = if (wasm_bits != int_bits) 6719 try (try func.signExtendInt(rhs, ty)).toLocal(func, ty) 6720 else 6721 rhs; 6722 6723 const zero = switch (wasm_bits) { 6724 32 => WValue{ .imm32 = 0 }, 6725 64 => WValue{ .imm64 = 0 }, 6726 else => unreachable, 6727 }; 6728 6729 // tee leaves the value on the stack and stores it in a local. 6730 const quotient = try func.allocLocal(ty); 6731 _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .div); 6732 try func.addLabel(.local_tee, quotient.local.value); 6733 6734 // select takes a 32 bit value as the condition, so in the 64 bit case we use eqz to narrow 6735 // the 64 bit value we want to use as the condition to 32 bits. 6736 // This also inverts the condition (non 0 => 0, 0 => 1), so we put the adjusted and 6737 // non-adjusted quotients on the stack in the opposite order for 32 vs 64 bits. 6738 if (wasm_bits == 64) { 6739 try func.emitWValue(quotient); 6740 } 6741 6742 // 0 if the signs of rhs_wasm and lhs_wasm are the same, 1 otherwise. 6743 _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .xor); 6744 _ = try func.cmp(.stack, zero, ty, .lt); 6745 6746 switch (wasm_bits) { 6747 32 => { 6748 try func.addTag(.i32_sub); 6749 try func.emitWValue(quotient); 6750 }, 6751 64 => { 6752 try func.addTag(.i64_extend_i32_u); 6753 try func.addTag(.i64_sub); 6754 }, 6755 else => unreachable, 6756 } 6757 6758 _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .rem); 6759 6760 if (wasm_bits == 64) { 6761 try func.addTag(.i64_eqz); 6762 } 6763 6764 try func.addTag(.select); 6765 6766 // We need to zero the high bits because N bit comparisons consider all 32 or 64 bits, and 6767 // expect all but the lowest N bits to be 0. 6768 // TODO: Should we be zeroing the high bits here or should we be ignoring the high bits 6769 // when performing comparisons? 6770 if (int_bits != wasm_bits) { 6771 _ = try func.wrapOperand(.{ .stack = {} }, ty); 6772 } 6773 } else { 6774 const float_bits = ty.floatBits(func.target); 6775 if (float_bits > 64) { 6776 return func.fail("TODO: `@divFloor` for floats with bitsize: {d}", .{float_bits}); 6777 } 6778 const is_f16 = float_bits == 16; 6779 6780 const lhs_wasm = if (is_f16) try func.fpext(lhs, Type.f16, Type.f32) else lhs; 6781 const rhs_wasm = if (is_f16) try func.fpext(rhs, Type.f16, Type.f32) else rhs; 6782 6783 try func.emitWValue(lhs_wasm); 6784 try func.emitWValue(rhs_wasm); 6785 6786 switch (float_bits) { 6787 16, 32 => { 6788 try func.addTag(.f32_div); 6789 try func.addTag(.f32_floor); 6790 }, 6791 64 => { 6792 try func.addTag(.f64_div); 6793 try func.addTag(.f64_floor); 6794 }, 6795 else => unreachable, 6796 } 6797 6798 if (is_f16) { 6799 _ = try func.fptrunc(.{ .stack = {} }, Type.f32, Type.f16); 6800 } 6801 } 6802 6803 const result = try func.allocLocal(ty); 6804 try func.addLabel(.local_set, result.local.value); 6805 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 6806 } 6807 6808 fn divSigned(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type) InnerError!WValue { 6809 const mod = func.bin_file.base.comp.module.?; 6810 const int_bits = ty.intInfo(mod).bits; 6811 const wasm_bits = toWasmBits(int_bits) orelse { 6812 return func.fail("TODO: Implement signed division for integers with bitsize '{d}'", .{int_bits}); 6813 }; 6814 6815 if (wasm_bits == 128) { 6816 return func.fail("TODO: Implement signed division for 128-bit integerrs", .{}); 6817 } 6818 6819 if (wasm_bits != int_bits) { 6820 // Leave both values on the stack 6821 _ = try func.signExtendInt(lhs, ty); 6822 _ = try func.signExtendInt(rhs, ty); 6823 } else { 6824 try func.emitWValue(lhs); 6825 try func.emitWValue(rhs); 6826 } 6827 try func.addTag(.i32_div_s); 6828 6829 const result = try func.allocLocal(ty); 6830 try func.addLabel(.local_set, result.local.value); 6831 return result; 6832 } 6833 6834 /// Remainder after floor division, defined by: 6835 /// @divFloor(a, b) * b + @mod(a, b) = a 6836 fn airMod(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 6837 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 6838 6839 const mod = func.bin_file.base.comp.module.?; 6840 const ty = func.typeOfIndex(inst); 6841 const lhs = try func.resolveInst(bin_op.lhs); 6842 const rhs = try func.resolveInst(bin_op.rhs); 6843 6844 if (ty.isUnsignedInt(mod)) { 6845 _ = try func.binOp(lhs, rhs, ty, .rem); 6846 } else if (ty.isSignedInt(mod)) { 6847 // The wasm rem instruction gives the remainder after truncating division (rounding towards 6848 // 0), equivalent to @rem. 6849 // We make use of the fact that: 6850 // @mod(a, b) = @rem(@rem(a, b) + b, b) 6851 const int_bits = ty.intInfo(mod).bits; 6852 const wasm_bits = toWasmBits(int_bits) orelse { 6853 return func.fail("TODO: `@mod` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits}); 6854 }; 6855 6856 if (wasm_bits > 64) { 6857 return func.fail("TODO: `@mod` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits}); 6858 } 6859 6860 const lhs_wasm = if (wasm_bits != int_bits) 6861 try (try func.signExtendInt(lhs, ty)).toLocal(func, ty) 6862 else 6863 lhs; 6864 6865 const rhs_wasm = if (wasm_bits != int_bits) 6866 try (try func.signExtendInt(rhs, ty)).toLocal(func, ty) 6867 else 6868 rhs; 6869 6870 _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .rem); 6871 _ = try func.binOp(.stack, rhs_wasm, ty, .add); 6872 _ = try func.binOp(.stack, rhs_wasm, ty, .rem); 6873 } else { 6874 return func.fail("TODO: implement `@mod` on floating point types for {}", .{func.target.cpu.arch}); 6875 } 6876 6877 const result = try func.allocLocal(ty); 6878 try func.addLabel(.local_set, result.local.value); 6879 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 6880 } 6881 6882 /// Sign extends an N bit signed integer and pushes the result to the stack. 6883 /// The result will be sign extended to 32 bits if N <= 32 or 64 bits if N <= 64. 6884 /// Support for integers wider than 64 bits has not yet been implemented. 6885 fn signExtendInt(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue { 6886 const mod = func.bin_file.base.comp.module.?; 6887 const int_bits = ty.intInfo(mod).bits; 6888 const wasm_bits = toWasmBits(int_bits) orelse { 6889 return func.fail("TODO: signExtendInt for signed integers larger than '{d}' bits", .{int_bits}); 6890 }; 6891 6892 const shift_val = switch (wasm_bits) { 6893 32 => WValue{ .imm32 = wasm_bits - int_bits }, 6894 64 => WValue{ .imm64 = wasm_bits - int_bits }, 6895 else => return func.fail("TODO: signExtendInt for i128", .{}), 6896 }; 6897 6898 try func.emitWValue(operand); 6899 switch (wasm_bits) { 6900 32 => { 6901 try func.emitWValue(shift_val); 6902 try func.addTag(.i32_shl); 6903 try func.emitWValue(shift_val); 6904 try func.addTag(.i32_shr_s); 6905 }, 6906 64 => { 6907 try func.emitWValue(shift_val); 6908 try func.addTag(.i64_shl); 6909 try func.emitWValue(shift_val); 6910 try func.addTag(.i64_shr_s); 6911 }, 6912 else => unreachable, 6913 } 6914 6915 return WValue{ .stack = {} }; 6916 } 6917 6918 fn airSatBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void { 6919 assert(op == .add or op == .sub); 6920 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 6921 6922 const mod = func.bin_file.base.comp.module.?; 6923 const ty = func.typeOfIndex(inst); 6924 const lhs = try func.resolveInst(bin_op.lhs); 6925 const rhs = try func.resolveInst(bin_op.rhs); 6926 6927 const int_info = ty.intInfo(mod); 6928 const is_signed = int_info.signedness == .signed; 6929 6930 if (int_info.bits > 64) { 6931 return func.fail("TODO: saturating arithmetic for integers with bitsize '{d}'", .{int_info.bits}); 6932 } 6933 6934 if (is_signed) { 6935 const result = try signedSat(func, lhs, rhs, ty, op); 6936 return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 6937 } 6938 6939 const wasm_bits = toWasmBits(int_info.bits).?; 6940 var bin_result = try (try func.binOp(lhs, rhs, ty, op)).toLocal(func, ty); 6941 defer bin_result.free(func); 6942 if (wasm_bits != int_info.bits and op == .add) { 6943 const val: u64 = @as(u64, @intCast((@as(u65, 1) << @as(u7, @intCast(int_info.bits))) - 1)); 6944 const imm_val = switch (wasm_bits) { 6945 32 => WValue{ .imm32 = @as(u32, @intCast(val)) }, 6946 64 => WValue{ .imm64 = val }, 6947 else => unreachable, 6948 }; 6949 6950 try func.emitWValue(bin_result); 6951 try func.emitWValue(imm_val); 6952 _ = try func.cmp(bin_result, imm_val, ty, .lt); 6953 } else { 6954 switch (wasm_bits) { 6955 32 => try func.addImm32(if (op == .add) @as(i32, -1) else 0), 6956 64 => try func.addImm64(if (op == .add) @as(u64, @bitCast(@as(i64, -1))) else 0), 6957 else => unreachable, 6958 } 6959 try func.emitWValue(bin_result); 6960 _ = try func.cmp(bin_result, lhs, ty, if (op == .add) .lt else .gt); 6961 } 6962 6963 try func.addTag(.select); 6964 const result = try func.allocLocal(ty); 6965 try func.addLabel(.local_set, result.local.value); 6966 return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 6967 } 6968 6969 fn signedSat(func: *CodeGen, lhs_operand: WValue, rhs_operand: WValue, ty: Type, op: Op) InnerError!WValue { 6970 const mod = func.bin_file.base.comp.module.?; 6971 const int_info = ty.intInfo(mod); 6972 const wasm_bits = toWasmBits(int_info.bits).?; 6973 const is_wasm_bits = wasm_bits == int_info.bits; 6974 const ext_ty = if (!is_wasm_bits) try mod.intType(int_info.signedness, wasm_bits) else ty; 6975 6976 var lhs = if (!is_wasm_bits) lhs: { 6977 break :lhs try (try func.signExtendInt(lhs_operand, ty)).toLocal(func, ext_ty); 6978 } else lhs_operand; 6979 var rhs = if (!is_wasm_bits) rhs: { 6980 break :rhs try (try func.signExtendInt(rhs_operand, ty)).toLocal(func, ext_ty); 6981 } else rhs_operand; 6982 6983 const max_val: u64 = @as(u64, @intCast((@as(u65, 1) << @as(u7, @intCast(int_info.bits - 1))) - 1)); 6984 const min_val: i64 = (-@as(i64, @intCast(@as(u63, @intCast(max_val))))) - 1; 6985 const max_wvalue = switch (wasm_bits) { 6986 32 => WValue{ .imm32 = @as(u32, @truncate(max_val)) }, 6987 64 => WValue{ .imm64 = max_val }, 6988 else => unreachable, 6989 }; 6990 const min_wvalue = switch (wasm_bits) { 6991 32 => WValue{ .imm32 = @as(u32, @bitCast(@as(i32, @truncate(min_val)))) }, 6992 64 => WValue{ .imm64 = @as(u64, @bitCast(min_val)) }, 6993 else => unreachable, 6994 }; 6995 6996 var bin_result = try (try func.binOp(lhs, rhs, ext_ty, op)).toLocal(func, ext_ty); 6997 if (!is_wasm_bits) { 6998 defer bin_result.free(func); // not returned in this branch 6999 defer lhs.free(func); // uses temporary local for absvalue 7000 defer rhs.free(func); // uses temporary local for absvalue 7001 try func.emitWValue(bin_result); 7002 try func.emitWValue(max_wvalue); 7003 _ = try func.cmp(bin_result, max_wvalue, ext_ty, .lt); 7004 try func.addTag(.select); 7005 try func.addLabel(.local_set, bin_result.local.value); // re-use local 7006 7007 try func.emitWValue(bin_result); 7008 try func.emitWValue(min_wvalue); 7009 _ = try func.cmp(bin_result, min_wvalue, ext_ty, .gt); 7010 try func.addTag(.select); 7011 try func.addLabel(.local_set, bin_result.local.value); // re-use local 7012 return (try func.wrapOperand(bin_result, ty)).toLocal(func, ty); 7013 } else { 7014 const zero = switch (wasm_bits) { 7015 32 => WValue{ .imm32 = 0 }, 7016 64 => WValue{ .imm64 = 0 }, 7017 else => unreachable, 7018 }; 7019 try func.emitWValue(max_wvalue); 7020 try func.emitWValue(min_wvalue); 7021 _ = try func.cmp(bin_result, zero, ty, .lt); 7022 try func.addTag(.select); 7023 try func.emitWValue(bin_result); 7024 // leave on stack 7025 const cmp_zero_result = try func.cmp(rhs, zero, ty, if (op == .add) .lt else .gt); 7026 const cmp_bin_result = try func.cmp(bin_result, lhs, ty, .lt); 7027 _ = try func.binOp(cmp_zero_result, cmp_bin_result, Type.u32, .xor); // comparisons always return i32, so provide u32 as type to xor. 7028 try func.addTag(.select); 7029 try func.addLabel(.local_set, bin_result.local.value); // re-use local 7030 return bin_result; 7031 } 7032 } 7033 7034 fn airShlSat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 7035 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 7036 7037 const mod = func.bin_file.base.comp.module.?; 7038 const ty = func.typeOfIndex(inst); 7039 const int_info = ty.intInfo(mod); 7040 const is_signed = int_info.signedness == .signed; 7041 if (int_info.bits > 64) { 7042 return func.fail("TODO: Saturating shifting left for integers with bitsize '{d}'", .{int_info.bits}); 7043 } 7044 7045 const lhs = try func.resolveInst(bin_op.lhs); 7046 const rhs = try func.resolveInst(bin_op.rhs); 7047 const wasm_bits = toWasmBits(int_info.bits).?; 7048 const result = try func.allocLocal(ty); 7049 7050 if (wasm_bits == int_info.bits) outer_blk: { 7051 var shl = try (try func.binOp(lhs, rhs, ty, .shl)).toLocal(func, ty); 7052 defer shl.free(func); 7053 var shr = try (try func.binOp(shl, rhs, ty, .shr)).toLocal(func, ty); 7054 defer shr.free(func); 7055 7056 switch (wasm_bits) { 7057 32 => blk: { 7058 if (!is_signed) { 7059 try func.addImm32(-1); 7060 break :blk; 7061 } 7062 try func.addImm32(std.math.minInt(i32)); 7063 try func.addImm32(std.math.maxInt(i32)); 7064 _ = try func.cmp(lhs, .{ .imm32 = 0 }, ty, .lt); 7065 try func.addTag(.select); 7066 }, 7067 64 => blk: { 7068 if (!is_signed) { 7069 try func.addImm64(@as(u64, @bitCast(@as(i64, -1)))); 7070 break :blk; 7071 } 7072 try func.addImm64(@as(u64, @bitCast(@as(i64, std.math.minInt(i64))))); 7073 try func.addImm64(@as(u64, @bitCast(@as(i64, std.math.maxInt(i64))))); 7074 _ = try func.cmp(lhs, .{ .imm64 = 0 }, ty, .lt); 7075 try func.addTag(.select); 7076 }, 7077 else => unreachable, 7078 } 7079 try func.emitWValue(shl); 7080 _ = try func.cmp(lhs, shr, ty, .neq); 7081 try func.addTag(.select); 7082 try func.addLabel(.local_set, result.local.value); 7083 break :outer_blk; 7084 } else { 7085 const shift_size = wasm_bits - int_info.bits; 7086 const shift_value = switch (wasm_bits) { 7087 32 => WValue{ .imm32 = shift_size }, 7088 64 => WValue{ .imm64 = shift_size }, 7089 else => unreachable, 7090 }; 7091 const ext_ty = try mod.intType(int_info.signedness, wasm_bits); 7092 7093 var shl_res = try (try func.binOp(lhs, shift_value, ext_ty, .shl)).toLocal(func, ext_ty); 7094 defer shl_res.free(func); 7095 var shl = try (try func.binOp(shl_res, rhs, ext_ty, .shl)).toLocal(func, ext_ty); 7096 defer shl.free(func); 7097 var shr = try (try func.binOp(shl, rhs, ext_ty, .shr)).toLocal(func, ext_ty); 7098 defer shr.free(func); 7099 7100 switch (wasm_bits) { 7101 32 => blk: { 7102 if (!is_signed) { 7103 try func.addImm32(-1); 7104 break :blk; 7105 } 7106 7107 try func.addImm32(std.math.minInt(i32)); 7108 try func.addImm32(std.math.maxInt(i32)); 7109 _ = try func.cmp(shl_res, .{ .imm32 = 0 }, ext_ty, .lt); 7110 try func.addTag(.select); 7111 }, 7112 64 => blk: { 7113 if (!is_signed) { 7114 try func.addImm64(@as(u64, @bitCast(@as(i64, -1)))); 7115 break :blk; 7116 } 7117 7118 try func.addImm64(@as(u64, @bitCast(@as(i64, std.math.minInt(i64))))); 7119 try func.addImm64(@as(u64, @bitCast(@as(i64, std.math.maxInt(i64))))); 7120 _ = try func.cmp(shl_res, .{ .imm64 = 0 }, ext_ty, .lt); 7121 try func.addTag(.select); 7122 }, 7123 else => unreachable, 7124 } 7125 try func.emitWValue(shl); 7126 _ = try func.cmp(shl_res, shr, ext_ty, .neq); 7127 try func.addTag(.select); 7128 try func.addLabel(.local_set, result.local.value); 7129 var shift_result = try func.binOp(result, shift_value, ext_ty, .shr); 7130 if (is_signed) { 7131 shift_result = try func.wrapOperand(shift_result, ty); 7132 } 7133 try func.addLabel(.local_set, result.local.value); 7134 } 7135 7136 return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); 7137 } 7138 7139 /// Calls a compiler-rt intrinsic by creating an undefined symbol, 7140 /// then lowering the arguments and calling the symbol as a function call. 7141 /// This function call assumes the C-ABI. 7142 /// Asserts arguments are not stack values when the return value is 7143 /// passed as the first parameter. 7144 /// May leave the return value on the stack. 7145 fn callIntrinsic( 7146 func: *CodeGen, 7147 name: []const u8, 7148 param_types: []const InternPool.Index, 7149 return_type: Type, 7150 args: []const WValue, 7151 ) InnerError!WValue { 7152 assert(param_types.len == args.len); 7153 const symbol_index = func.bin_file.base.getGlobalSymbol(name, null) catch |err| { 7154 return func.fail("Could not find or create global symbol '{s}'", .{@errorName(err)}); 7155 }; 7156 7157 // Always pass over C-ABI 7158 const mod = func.bin_file.base.comp.module.?; 7159 var func_type = try genFunctype(func.gpa, .C, param_types, return_type, mod); 7160 defer func_type.deinit(func.gpa); 7161 const func_type_index = try func.bin_file.putOrGetFuncType(func_type); 7162 try func.bin_file.addOrUpdateImport(name, symbol_index, null, func_type_index); 7163 7164 const want_sret_param = firstParamSRet(.C, return_type, mod); 7165 // if we want return as first param, we allocate a pointer to stack, 7166 // and emit it as our first argument 7167 const sret = if (want_sret_param) blk: { 7168 const sret_local = try func.allocStack(return_type); 7169 try func.lowerToStack(sret_local); 7170 break :blk sret_local; 7171 } else WValue{ .none = {} }; 7172 7173 // Lower all arguments to the stack before we call our function 7174 for (args, 0..) |arg, arg_i| { 7175 assert(!(want_sret_param and arg == .stack)); 7176 assert(Type.fromInterned(param_types[arg_i]).hasRuntimeBitsIgnoreComptime(mod)); 7177 try func.lowerArg(.C, Type.fromInterned(param_types[arg_i]), arg); 7178 } 7179 7180 // Actually call our intrinsic 7181 try func.addLabel(.call, symbol_index); 7182 7183 if (!return_type.hasRuntimeBitsIgnoreComptime(mod)) { 7184 return WValue.none; 7185 } else if (return_type.isNoReturn(mod)) { 7186 try func.addTag(.@"unreachable"); 7187 return WValue.none; 7188 } else if (want_sret_param) { 7189 return sret; 7190 } else { 7191 return WValue{ .stack = {} }; 7192 } 7193 } 7194 7195 fn airTagName(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 7196 const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 7197 const operand = try func.resolveInst(un_op); 7198 const enum_ty = func.typeOf(un_op); 7199 7200 const func_sym_index = try func.getTagNameFunction(enum_ty); 7201 7202 const result_ptr = try func.allocStack(func.typeOfIndex(inst)); 7203 try func.lowerToStack(result_ptr); 7204 try func.emitWValue(operand); 7205 try func.addLabel(.call, func_sym_index); 7206 7207 return func.finishAir(inst, result_ptr, &.{un_op}); 7208 } 7209 7210 fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 { 7211 const mod = func.bin_file.base.comp.module.?; 7212 const enum_decl_index = enum_ty.getOwnerDecl(mod); 7213 7214 var arena_allocator = std.heap.ArenaAllocator.init(func.gpa); 7215 defer arena_allocator.deinit(); 7216 const arena = arena_allocator.allocator(); 7217 7218 const fqn = mod.intern_pool.stringToSlice(try mod.declPtr(enum_decl_index).getFullyQualifiedName(mod)); 7219 const func_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{s}", .{fqn}); 7220 7221 // check if we already generated code for this. 7222 if (func.bin_file.findGlobalSymbol(func_name)) |loc| { 7223 return loc.index; 7224 } 7225 7226 const int_tag_ty = enum_ty.intTagType(mod); 7227 7228 if (int_tag_ty.bitSize(mod) > 64) { 7229 return func.fail("TODO: Implement @tagName for enums with tag size larger than 64 bits", .{}); 7230 } 7231 7232 var relocs = std.ArrayList(link.File.Wasm.Relocation).init(func.gpa); 7233 defer relocs.deinit(); 7234 7235 var body_list = std.ArrayList(u8).init(func.gpa); 7236 defer body_list.deinit(); 7237 var writer = body_list.writer(); 7238 7239 // The locals of the function body (always 0) 7240 try leb.writeULEB128(writer, @as(u32, 0)); 7241 7242 // outer block 7243 try writer.writeByte(std.wasm.opcode(.block)); 7244 try writer.writeByte(std.wasm.block_empty); 7245 7246 // TODO: Make switch implementation generic so we can use a jump table for this when the tags are not sparse. 7247 // generate an if-else chain for each tag value as well as constant. 7248 for (enum_ty.enumFields(mod), 0..) |tag_name_ip, field_index_usize| { 7249 const field_index = @as(u32, @intCast(field_index_usize)); 7250 const tag_name = mod.intern_pool.stringToSlice(tag_name_ip); 7251 // for each tag name, create an unnamed const, 7252 // and then get a pointer to its value. 7253 const name_ty = try mod.arrayType(.{ 7254 .len = tag_name.len, 7255 .child = .u8_type, 7256 .sentinel = .zero_u8, 7257 }); 7258 const name_val = try mod.intern(.{ .aggregate = .{ 7259 .ty = name_ty.toIntern(), 7260 .storage = .{ .bytes = tag_name }, 7261 } }); 7262 const tag_sym_index = try func.bin_file.lowerUnnamedConst( 7263 .{ .ty = name_ty, .val = Value.fromInterned(name_val) }, 7264 enum_decl_index, 7265 ); 7266 7267 // block for this if case 7268 try writer.writeByte(std.wasm.opcode(.block)); 7269 try writer.writeByte(std.wasm.block_empty); 7270 7271 // get actual tag value (stored in 2nd parameter); 7272 try writer.writeByte(std.wasm.opcode(.local_get)); 7273 try leb.writeULEB128(writer, @as(u32, 1)); 7274 7275 const tag_val = try mod.enumValueFieldIndex(enum_ty, field_index); 7276 const tag_value = try func.lowerConstant(tag_val, enum_ty); 7277 7278 switch (tag_value) { 7279 .imm32 => |value| { 7280 try writer.writeByte(std.wasm.opcode(.i32_const)); 7281 try leb.writeILEB128(writer, @as(i32, @bitCast(value))); 7282 try writer.writeByte(std.wasm.opcode(.i32_ne)); 7283 }, 7284 .imm64 => |value| { 7285 try writer.writeByte(std.wasm.opcode(.i64_const)); 7286 try leb.writeILEB128(writer, @as(i64, @bitCast(value))); 7287 try writer.writeByte(std.wasm.opcode(.i64_ne)); 7288 }, 7289 else => unreachable, 7290 } 7291 // if they're not equal, break out of current branch 7292 try writer.writeByte(std.wasm.opcode(.br_if)); 7293 try leb.writeULEB128(writer, @as(u32, 0)); 7294 7295 // store the address of the tagname in the pointer field of the slice 7296 // get the address twice so we can also store the length. 7297 try writer.writeByte(std.wasm.opcode(.local_get)); 7298 try leb.writeULEB128(writer, @as(u32, 0)); 7299 try writer.writeByte(std.wasm.opcode(.local_get)); 7300 try leb.writeULEB128(writer, @as(u32, 0)); 7301 7302 // get address of tagname and emit a relocation to it 7303 if (func.arch() == .wasm32) { 7304 const encoded_alignment = @ctz(@as(u32, 4)); 7305 try writer.writeByte(std.wasm.opcode(.i32_const)); 7306 try relocs.append(.{ 7307 .relocation_type = .R_WASM_MEMORY_ADDR_LEB, 7308 .offset = @as(u32, @intCast(body_list.items.len)), 7309 .index = tag_sym_index, 7310 }); 7311 try writer.writeAll(&[_]u8{0} ** 5); // will be relocated 7312 7313 // store pointer 7314 try writer.writeByte(std.wasm.opcode(.i32_store)); 7315 try leb.writeULEB128(writer, encoded_alignment); 7316 try leb.writeULEB128(writer, @as(u32, 0)); 7317 7318 // store length 7319 try writer.writeByte(std.wasm.opcode(.i32_const)); 7320 try leb.writeULEB128(writer, @as(u32, @intCast(tag_name.len))); 7321 try writer.writeByte(std.wasm.opcode(.i32_store)); 7322 try leb.writeULEB128(writer, encoded_alignment); 7323 try leb.writeULEB128(writer, @as(u32, 4)); 7324 } else { 7325 const encoded_alignment = @ctz(@as(u32, 8)); 7326 try writer.writeByte(std.wasm.opcode(.i64_const)); 7327 try relocs.append(.{ 7328 .relocation_type = .R_WASM_MEMORY_ADDR_LEB64, 7329 .offset = @as(u32, @intCast(body_list.items.len)), 7330 .index = tag_sym_index, 7331 }); 7332 try writer.writeAll(&[_]u8{0} ** 10); // will be relocated 7333 7334 // store pointer 7335 try writer.writeByte(std.wasm.opcode(.i64_store)); 7336 try leb.writeULEB128(writer, encoded_alignment); 7337 try leb.writeULEB128(writer, @as(u32, 0)); 7338 7339 // store length 7340 try writer.writeByte(std.wasm.opcode(.i64_const)); 7341 try leb.writeULEB128(writer, @as(u64, @intCast(tag_name.len))); 7342 try writer.writeByte(std.wasm.opcode(.i64_store)); 7343 try leb.writeULEB128(writer, encoded_alignment); 7344 try leb.writeULEB128(writer, @as(u32, 8)); 7345 } 7346 7347 // break outside blocks 7348 try writer.writeByte(std.wasm.opcode(.br)); 7349 try leb.writeULEB128(writer, @as(u32, 1)); 7350 7351 // end the block for this case 7352 try writer.writeByte(std.wasm.opcode(.end)); 7353 } 7354 7355 try writer.writeByte(std.wasm.opcode(.@"unreachable")); // tag value does not have a name 7356 // finish outer block 7357 try writer.writeByte(std.wasm.opcode(.end)); 7358 // finish function body 7359 try writer.writeByte(std.wasm.opcode(.end)); 7360 7361 const slice_ty = Type.slice_const_u8_sentinel_0; 7362 const func_type = try genFunctype(arena, .Unspecified, &.{int_tag_ty.ip_index}, slice_ty, mod); 7363 return func.bin_file.createFunction(func_name, func_type, &body_list, &relocs); 7364 } 7365 7366 fn airErrorSetHasValue(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 7367 const mod = func.bin_file.base.comp.module.?; 7368 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 7369 7370 const operand = try func.resolveInst(ty_op.operand); 7371 const error_set_ty = ty_op.ty.toType(); 7372 const result = try func.allocLocal(Type.bool); 7373 7374 const names = error_set_ty.errorSetNames(mod); 7375 var values = try std.ArrayList(u32).initCapacity(func.gpa, names.len); 7376 defer values.deinit(); 7377 7378 var lowest: ?u32 = null; 7379 var highest: ?u32 = null; 7380 for (names) |name| { 7381 const err_int = @as(Module.ErrorInt, @intCast(mod.global_error_set.getIndex(name).?)); 7382 if (lowest) |*l| { 7383 if (err_int < l.*) { 7384 l.* = err_int; 7385 } 7386 } else { 7387 lowest = err_int; 7388 } 7389 if (highest) |*h| { 7390 if (err_int > h.*) { 7391 highest = err_int; 7392 } 7393 } else { 7394 highest = err_int; 7395 } 7396 7397 values.appendAssumeCapacity(err_int); 7398 } 7399 7400 // start block for 'true' branch 7401 try func.startBlock(.block, wasm.block_empty); 7402 // start block for 'false' branch 7403 try func.startBlock(.block, wasm.block_empty); 7404 // block for the jump table itself 7405 try func.startBlock(.block, wasm.block_empty); 7406 7407 // lower operand to determine jump table target 7408 try func.emitWValue(operand); 7409 try func.addImm32(@as(i32, @intCast(lowest.?))); 7410 try func.addTag(.i32_sub); 7411 7412 // Account for default branch so always add '1' 7413 const depth = @as(u32, @intCast(highest.? - lowest.? + 1)); 7414 const jump_table: Mir.JumpTable = .{ .length = depth }; 7415 const table_extra_index = try func.addExtra(jump_table); 7416 try func.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } }); 7417 try func.mir_extra.ensureUnusedCapacity(func.gpa, depth); 7418 7419 var value: u32 = lowest.?; 7420 while (value <= highest.?) : (value += 1) { 7421 const idx: u32 = blk: { 7422 for (values.items) |val| { 7423 if (val == value) break :blk 1; 7424 } 7425 break :blk 0; 7426 }; 7427 func.mir_extra.appendAssumeCapacity(idx); 7428 } 7429 try func.endBlock(); 7430 7431 // 'false' branch (i.e. error set does not have value 7432 // ensure we set local to 0 in case the local was re-used. 7433 try func.addImm32(0); 7434 try func.addLabel(.local_set, result.local.value); 7435 try func.addLabel(.br, 1); 7436 try func.endBlock(); 7437 7438 // 'true' branch 7439 try func.addImm32(1); 7440 try func.addLabel(.local_set, result.local.value); 7441 try func.addLabel(.br, 0); 7442 try func.endBlock(); 7443 7444 return func.finishAir(inst, result, &.{ty_op.operand}); 7445 } 7446 7447 inline fn useAtomicFeature(func: *const CodeGen) bool { 7448 return std.Target.wasm.featureSetHas(func.target.cpu.features, .atomics); 7449 } 7450 7451 fn airCmpxchg(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 7452 const mod = func.bin_file.base.comp.module.?; 7453 const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 7454 const extra = func.air.extraData(Air.Cmpxchg, ty_pl.payload).data; 7455 7456 const ptr_ty = func.typeOf(extra.ptr); 7457 const ty = ptr_ty.childType(mod); 7458 const result_ty = func.typeOfIndex(inst); 7459 7460 const ptr_operand = try func.resolveInst(extra.ptr); 7461 const expected_val = try func.resolveInst(extra.expected_value); 7462 const new_val = try func.resolveInst(extra.new_value); 7463 7464 const cmp_result = try func.allocLocal(Type.bool); 7465 7466 const ptr_val = if (func.useAtomicFeature()) val: { 7467 const val_local = try func.allocLocal(ty); 7468 try func.emitWValue(ptr_operand); 7469 try func.lowerToStack(expected_val); 7470 try func.lowerToStack(new_val); 7471 try func.addAtomicMemArg(switch (ty.abiSize(mod)) { 7472 1 => .i32_atomic_rmw8_cmpxchg_u, 7473 2 => .i32_atomic_rmw16_cmpxchg_u, 7474 4 => .i32_atomic_rmw_cmpxchg, 7475 8 => .i32_atomic_rmw_cmpxchg, 7476 else => |size| return func.fail("TODO: implement `@cmpxchg` for types with abi size '{d}'", .{size}), 7477 }, .{ 7478 .offset = ptr_operand.offset(), 7479 .alignment = @intCast(ty.abiAlignment(mod).toByteUnitsOptional().?), 7480 }); 7481 try func.addLabel(.local_tee, val_local.local.value); 7482 _ = try func.cmp(.stack, expected_val, ty, .eq); 7483 try func.addLabel(.local_set, cmp_result.local.value); 7484 break :val val_local; 7485 } else val: { 7486 if (ty.abiSize(mod) > 8) { 7487 return func.fail("TODO: Implement `@cmpxchg` for types larger than abi size of 8 bytes", .{}); 7488 } 7489 const ptr_val = try WValue.toLocal(try func.load(ptr_operand, ty, 0), func, ty); 7490 7491 try func.lowerToStack(ptr_operand); 7492 try func.lowerToStack(new_val); 7493 try func.emitWValue(ptr_val); 7494 _ = try func.cmp(ptr_val, expected_val, ty, .eq); 7495 try func.addLabel(.local_tee, cmp_result.local.value); 7496 try func.addTag(.select); 7497 try func.store(.stack, .stack, ty, 0); 7498 7499 break :val ptr_val; 7500 }; 7501 7502 const result_ptr = if (isByRef(result_ty, mod)) val: { 7503 try func.emitWValue(cmp_result); 7504 try func.addImm32(-1); 7505 try func.addTag(.i32_xor); 7506 try func.addImm32(1); 7507 try func.addTag(.i32_and); 7508 const and_result = try WValue.toLocal(.stack, func, Type.bool); 7509 const result_ptr = try func.allocStack(result_ty); 7510 try func.store(result_ptr, and_result, Type.bool, @as(u32, @intCast(ty.abiSize(mod)))); 7511 try func.store(result_ptr, ptr_val, ty, 0); 7512 break :val result_ptr; 7513 } else val: { 7514 try func.addImm32(0); 7515 try func.emitWValue(ptr_val); 7516 try func.emitWValue(cmp_result); 7517 try func.addTag(.select); 7518 break :val try WValue.toLocal(.stack, func, result_ty); 7519 }; 7520 7521 return func.finishAir(inst, result_ptr, &.{ extra.ptr, extra.expected_value, extra.new_value }); 7522 } 7523 7524 fn airAtomicLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 7525 const mod = func.bin_file.base.comp.module.?; 7526 const atomic_load = func.air.instructions.items(.data)[@intFromEnum(inst)].atomic_load; 7527 const ptr = try func.resolveInst(atomic_load.ptr); 7528 const ty = func.typeOfIndex(inst); 7529 7530 if (func.useAtomicFeature()) { 7531 const tag: wasm.AtomicsOpcode = switch (ty.abiSize(mod)) { 7532 1 => .i32_atomic_load8_u, 7533 2 => .i32_atomic_load16_u, 7534 4 => .i32_atomic_load, 7535 8 => .i64_atomic_load, 7536 else => |size| return func.fail("TODO: @atomicLoad for types with abi size {d}", .{size}), 7537 }; 7538 try func.emitWValue(ptr); 7539 try func.addAtomicMemArg(tag, .{ 7540 .offset = ptr.offset(), 7541 .alignment = @intCast(ty.abiAlignment(mod).toByteUnitsOptional().?), 7542 }); 7543 } else { 7544 _ = try func.load(ptr, ty, 0); 7545 } 7546 7547 const result = try WValue.toLocal(.stack, func, ty); 7548 return func.finishAir(inst, result, &.{atomic_load.ptr}); 7549 } 7550 7551 fn airAtomicRmw(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 7552 const mod = func.bin_file.base.comp.module.?; 7553 const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 7554 const extra = func.air.extraData(Air.AtomicRmw, pl_op.payload).data; 7555 7556 const ptr = try func.resolveInst(pl_op.operand); 7557 const operand = try func.resolveInst(extra.operand); 7558 const ty = func.typeOfIndex(inst); 7559 const op: std.builtin.AtomicRmwOp = extra.op(); 7560 7561 if (func.useAtomicFeature()) { 7562 switch (op) { 7563 .Max, 7564 .Min, 7565 .Nand, 7566 => { 7567 const tmp = try func.load(ptr, ty, 0); 7568 const value = try tmp.toLocal(func, ty); 7569 7570 // create a loop to cmpxchg the new value 7571 try func.startBlock(.loop, wasm.block_empty); 7572 7573 try func.emitWValue(ptr); 7574 try func.emitWValue(value); 7575 if (op == .Nand) { 7576 const wasm_bits = toWasmBits(@as(u16, @intCast(ty.bitSize(mod)))).?; 7577 7578 const and_res = try func.binOp(value, operand, ty, .@"and"); 7579 if (wasm_bits == 32) 7580 try func.addImm32(-1) 7581 else if (wasm_bits == 64) 7582 try func.addImm64(@as(u64, @bitCast(@as(i64, -1)))) 7583 else 7584 return func.fail("TODO: `@atomicRmw` with operator `Nand` for types larger than 64 bits", .{}); 7585 _ = try func.binOp(and_res, .stack, ty, .xor); 7586 } else { 7587 try func.emitWValue(value); 7588 try func.emitWValue(operand); 7589 _ = try func.cmp(value, operand, ty, if (op == .Max) .gt else .lt); 7590 try func.addTag(.select); 7591 } 7592 try func.addAtomicMemArg( 7593 switch (ty.abiSize(mod)) { 7594 1 => .i32_atomic_rmw8_cmpxchg_u, 7595 2 => .i32_atomic_rmw16_cmpxchg_u, 7596 4 => .i32_atomic_rmw_cmpxchg, 7597 8 => .i64_atomic_rmw_cmpxchg, 7598 else => return func.fail("TODO: implement `@atomicRmw` with operation `{s}` for types larger than 64 bits", .{@tagName(op)}), 7599 }, 7600 .{ 7601 .offset = ptr.offset(), 7602 .alignment = @intCast(ty.abiAlignment(mod).toByteUnitsOptional().?), 7603 }, 7604 ); 7605 const select_res = try func.allocLocal(ty); 7606 try func.addLabel(.local_tee, select_res.local.value); 7607 _ = try func.cmp(.stack, value, ty, .neq); // leave on stack so we can use it for br_if 7608 7609 try func.emitWValue(select_res); 7610 try func.addLabel(.local_set, value.local.value); 7611 7612 try func.addLabel(.br_if, 0); 7613 try func.endBlock(); 7614 return func.finishAir(inst, value, &.{ pl_op.operand, extra.operand }); 7615 }, 7616 7617 // the other operations have their own instructions for Wasm. 7618 else => { 7619 try func.emitWValue(ptr); 7620 try func.emitWValue(operand); 7621 const tag: wasm.AtomicsOpcode = switch (ty.abiSize(mod)) { 7622 1 => switch (op) { 7623 .Xchg => .i32_atomic_rmw8_xchg_u, 7624 .Add => .i32_atomic_rmw8_add_u, 7625 .Sub => .i32_atomic_rmw8_sub_u, 7626 .And => .i32_atomic_rmw8_and_u, 7627 .Or => .i32_atomic_rmw8_or_u, 7628 .Xor => .i32_atomic_rmw8_xor_u, 7629 else => unreachable, 7630 }, 7631 2 => switch (op) { 7632 .Xchg => .i32_atomic_rmw16_xchg_u, 7633 .Add => .i32_atomic_rmw16_add_u, 7634 .Sub => .i32_atomic_rmw16_sub_u, 7635 .And => .i32_atomic_rmw16_and_u, 7636 .Or => .i32_atomic_rmw16_or_u, 7637 .Xor => .i32_atomic_rmw16_xor_u, 7638 else => unreachable, 7639 }, 7640 4 => switch (op) { 7641 .Xchg => .i32_atomic_rmw_xchg, 7642 .Add => .i32_atomic_rmw_add, 7643 .Sub => .i32_atomic_rmw_sub, 7644 .And => .i32_atomic_rmw_and, 7645 .Or => .i32_atomic_rmw_or, 7646 .Xor => .i32_atomic_rmw_xor, 7647 else => unreachable, 7648 }, 7649 8 => switch (op) { 7650 .Xchg => .i64_atomic_rmw_xchg, 7651 .Add => .i64_atomic_rmw_add, 7652 .Sub => .i64_atomic_rmw_sub, 7653 .And => .i64_atomic_rmw_and, 7654 .Or => .i64_atomic_rmw_or, 7655 .Xor => .i64_atomic_rmw_xor, 7656 else => unreachable, 7657 }, 7658 else => |size| return func.fail("TODO: Implement `@atomicRmw` for types with abi size {d}", .{size}), 7659 }; 7660 try func.addAtomicMemArg(tag, .{ 7661 .offset = ptr.offset(), 7662 .alignment = @intCast(ty.abiAlignment(mod).toByteUnitsOptional().?), 7663 }); 7664 const result = try WValue.toLocal(.stack, func, ty); 7665 return func.finishAir(inst, result, &.{ pl_op.operand, extra.operand }); 7666 }, 7667 } 7668 } else { 7669 const loaded = try func.load(ptr, ty, 0); 7670 const result = try loaded.toLocal(func, ty); 7671 7672 switch (op) { 7673 .Xchg => { 7674 try func.store(ptr, operand, ty, 0); 7675 }, 7676 .Add, 7677 .Sub, 7678 .And, 7679 .Or, 7680 .Xor, 7681 => { 7682 try func.emitWValue(ptr); 7683 _ = try func.binOp(result, operand, ty, switch (op) { 7684 .Add => .add, 7685 .Sub => .sub, 7686 .And => .@"and", 7687 .Or => .@"or", 7688 .Xor => .xor, 7689 else => unreachable, 7690 }); 7691 if (ty.isInt(mod) and (op == .Add or op == .Sub)) { 7692 _ = try func.wrapOperand(.stack, ty); 7693 } 7694 try func.store(.stack, .stack, ty, ptr.offset()); 7695 }, 7696 .Max, 7697 .Min, 7698 => { 7699 try func.emitWValue(ptr); 7700 try func.emitWValue(result); 7701 try func.emitWValue(operand); 7702 _ = try func.cmp(result, operand, ty, if (op == .Max) .gt else .lt); 7703 try func.addTag(.select); 7704 try func.store(.stack, .stack, ty, ptr.offset()); 7705 }, 7706 .Nand => { 7707 const wasm_bits = toWasmBits(@as(u16, @intCast(ty.bitSize(mod)))).?; 7708 7709 try func.emitWValue(ptr); 7710 const and_res = try func.binOp(result, operand, ty, .@"and"); 7711 if (wasm_bits == 32) 7712 try func.addImm32(-1) 7713 else if (wasm_bits == 64) 7714 try func.addImm64(@as(u64, @bitCast(@as(i64, -1)))) 7715 else 7716 return func.fail("TODO: `@atomicRmw` with operator `Nand` for types larger than 64 bits", .{}); 7717 _ = try func.binOp(and_res, .stack, ty, .xor); 7718 try func.store(.stack, .stack, ty, ptr.offset()); 7719 }, 7720 } 7721 7722 return func.finishAir(inst, result, &.{ pl_op.operand, extra.operand }); 7723 } 7724 } 7725 7726 fn airFence(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 7727 // Only when the atomic feature is enabled, and we're not building 7728 // for a single-threaded build, can we emit the `fence` instruction. 7729 // In all other cases, we emit no instructions for a fence. 7730 if (func.useAtomicFeature() and !func.bin_file.base.options.single_threaded) { 7731 try func.addAtomicTag(.atomic_fence); 7732 } 7733 7734 return func.finishAir(inst, .none, &.{}); 7735 } 7736 7737 fn airAtomicStore(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 7738 const mod = func.bin_file.base.comp.module.?; 7739 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 7740 7741 const ptr = try func.resolveInst(bin_op.lhs); 7742 const operand = try func.resolveInst(bin_op.rhs); 7743 const ptr_ty = func.typeOf(bin_op.lhs); 7744 const ty = ptr_ty.childType(mod); 7745 7746 if (func.useAtomicFeature()) { 7747 const tag: wasm.AtomicsOpcode = switch (ty.abiSize(mod)) { 7748 1 => .i32_atomic_store8, 7749 2 => .i32_atomic_store16, 7750 4 => .i32_atomic_store, 7751 8 => .i64_atomic_store, 7752 else => |size| return func.fail("TODO: @atomicLoad for types with abi size {d}", .{size}), 7753 }; 7754 try func.emitWValue(ptr); 7755 try func.lowerToStack(operand); 7756 try func.addAtomicMemArg(tag, .{ 7757 .offset = ptr.offset(), 7758 .alignment = @intCast(ty.abiAlignment(mod).toByteUnitsOptional().?), 7759 }); 7760 } else { 7761 try func.store(ptr, operand, ty, 0); 7762 } 7763 7764 return func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); 7765 } 7766 7767 fn airFrameAddress(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { 7768 if (func.initial_stack_value == .none) { 7769 try func.initializeStack(); 7770 } 7771 try func.emitWValue(func.bottom_stack_value); 7772 const result = try WValue.toLocal(.stack, func, Type.usize); 7773 return func.finishAir(inst, result, &.{}); 7774 } 7775 7776 fn typeOf(func: *CodeGen, inst: Air.Inst.Ref) Type { 7777 const mod = func.bin_file.base.comp.module.?; 7778 return func.air.typeOf(inst, &mod.intern_pool); 7779 } 7780 7781 fn typeOfIndex(func: *CodeGen, inst: Air.Inst.Index) Type { 7782 const mod = func.bin_file.base.comp.module.?; 7783 return func.air.typeOfIndex(inst, &mod.intern_pool); 7784 }