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