| ... | ... | @@ -115,6 +115,8 @@ pub const Feature = enum { |
| 115 | 115 | scalarize_int_from_float_safe, |
| 116 | 116 | scalarize_int_from_float_optimized_safe, |
| 117 | 117 | scalarize_float_from_int, |
| 118 | scalarize_reduce, |
| 119 | scalarize_reduce_optimized, |
| 118 | 120 | scalarize_shuffle_one, |
| 119 | 121 | scalarize_shuffle_two, |
| 120 | 122 | scalarize_select, |
| ... | ... | @@ -159,6 +161,27 @@ pub const Feature = enum { |
| 159 | 161 | /// Replace `aggregate_init` of a packed struct with a sequence of `shl_exact`, `bitcast`, `intcast`, and `bit_or`. |
| 160 | 162 | expand_packed_aggregate_init, |
| 161 | 163 | |
| 164 | /// Replace all arithmetic operations on 16-bit floating-point types with calls to soft-float |
| 165 | /// routines in compiler_rt, including `fptrunc`/`fpext`/`float_from_int`/`int_from_float` |
| 166 | /// where the operand or target type is a 16-bit floating-point type. This feature implies: |
| 167 | /// |
| 168 | /// * scalarization of 16-bit float vector operations |
| 169 | /// * expansion of safety-checked 16-bit float operations |
| 170 | /// |
| 171 | /// If this feature is enabled, the following AIR instruction tags may be emitted: |
| 172 | /// * `.legalize_vec_elem_val` |
| 173 | /// * `.legalize_vec_store_elem` |
| 174 | /// * `.legalize_compiler_rt_call` |
| 175 | soft_f16, |
| 176 | /// Like `soft_f16`, but for 32-bit floating-point types. |
| 177 | soft_f32, |
| 178 | /// Like `soft_f16`, but for 64-bit floating-point types. |
| 179 | soft_f64, |
| 180 | /// Like `soft_f16`, but for 80-bit floating-point types. |
| 181 | soft_f80, |
| 182 | /// Like `soft_f16`, but for 128-bit floating-point types. |
| 183 | soft_f128, |
| 184 | |
| 162 | 185 | fn scalarize(tag: Air.Inst.Tag) Feature { |
| 163 | 186 | return switch (tag) { |
| 164 | 187 | else => unreachable, |
| ... | ... | @@ -238,6 +261,8 @@ pub const Feature = enum { |
| 238 | 261 | .int_from_float_safe => .scalarize_int_from_float_safe, |
| 239 | 262 | .int_from_float_optimized_safe => .scalarize_int_from_float_optimized_safe, |
| 240 | 263 | .float_from_int => .scalarize_float_from_int, |
| 264 | .reduce => .scalarize_reduce, |
| 265 | .reduce_optimized => .scalarize_reduce_optimized, |
| 241 | 266 | .shuffle_one => .scalarize_shuffle_one, |
| 242 | 267 | .shuffle_two => .scalarize_shuffle_two, |
| 243 | 268 | .select => .scalarize_select, |
| ... | ... | @@ -283,6 +308,10 @@ fn extraData(l: *const Legalize, comptime T: type, index: usize) @TypeOf(Air.ext |
| 283 | 308 | } |
| 284 | 309 | |
| 285 | 310 | fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 311 | // In zig1, this function needs a lot of eval branch quota, because all of the inlined feature |
| 312 | // checks are comptime-evaluated (to ensure unused features are not included in the binary). |
| 313 | @setEvalBranchQuota(4000); |
| 314 | |
| 286 | 315 | const zcu = l.pt.zcu; |
| 287 | 316 | const ip = &zcu.intern_pool; |
| 288 | 317 | for (0..body_len) |body_index| { |
| ... | ... | @@ -291,30 +320,67 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 291 | 320 | .arg => {}, |
| 292 | 321 | inline .add, |
| 293 | 322 | .add_optimized, |
| 294 | | .add_wrap, |
| 295 | | .add_sat, |
| 296 | 323 | .sub, |
| 297 | 324 | .sub_optimized, |
| 298 | | .sub_wrap, |
| 299 | | .sub_sat, |
| 300 | 325 | .mul, |
| 301 | 326 | .mul_optimized, |
| 302 | | .mul_wrap, |
| 303 | | .mul_sat, |
| 304 | 327 | .div_float, |
| 305 | 328 | .div_float_optimized, |
| 306 | | .div_trunc, |
| 307 | | .div_trunc_optimized, |
| 308 | | .div_floor, |
| 309 | | .div_floor_optimized, |
| 310 | 329 | .div_exact, |
| 311 | 330 | .div_exact_optimized, |
| 312 | 331 | .rem, |
| 313 | 332 | .rem_optimized, |
| 314 | | .mod, |
| 315 | | .mod_optimized, |
| 316 | | .max, |
| 317 | 333 | .min, |
| 334 | .max, |
| 335 | => |air_tag| { |
| 336 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 337 | const ty = l.typeOf(bin_op.lhs); |
| 338 | switch (l.wantScalarizeOrSoftFloat(air_tag, ty)) { |
| 339 | .none => {}, |
| 340 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .bin_op)), |
| 341 | .soft_float => continue :inst try l.compilerRtCall( |
| 342 | inst, |
| 343 | softFloatFunc(air_tag, ty, zcu), |
| 344 | &.{ bin_op.lhs, bin_op.rhs }, |
| 345 | l.typeOf(bin_op.lhs), |
| 346 | ), |
| 347 | } |
| 348 | }, |
| 349 | inline .div_trunc, |
| 350 | .div_trunc_optimized, |
| 351 | .div_floor, |
| 352 | .div_floor_optimized, |
| 353 | => |air_tag| { |
| 354 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 355 | switch (l.wantScalarizeOrSoftFloat(air_tag, l.typeOf(bin_op.lhs))) { |
| 356 | .none => {}, |
| 357 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .bin_op)), |
| 358 | .soft_float => continue :inst l.replaceInst(inst, .block, try l.softFloatDivTruncFloorBlockPayload( |
| 359 | inst, |
| 360 | bin_op.lhs, |
| 361 | bin_op.rhs, |
| 362 | air_tag, |
| 363 | )), |
| 364 | } |
| 365 | }, |
| 366 | inline .mod, .mod_optimized => |air_tag| { |
| 367 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 368 | switch (l.wantScalarizeOrSoftFloat(air_tag, l.typeOf(bin_op.lhs))) { |
| 369 | .none => {}, |
| 370 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .bin_op)), |
| 371 | .soft_float => continue :inst l.replaceInst(inst, .block, try l.softFloatModBlockPayload( |
| 372 | inst, |
| 373 | bin_op.lhs, |
| 374 | bin_op.rhs, |
| 375 | )), |
| 376 | } |
| 377 | }, |
| 378 | inline .add_wrap, |
| 379 | .add_sat, |
| 380 | .sub_wrap, |
| 381 | .sub_sat, |
| 382 | .mul_wrap, |
| 383 | .mul_sat, |
| 318 | 384 | .bit_and, |
| 319 | 385 | .bit_or, |
| 320 | 386 | .xor, |
| ... | ... | @@ -408,20 +474,80 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 408 | 474 | .popcount, |
| 409 | 475 | .byte_swap, |
| 410 | 476 | .bit_reverse, |
| 411 | | .abs, |
| 412 | | .fptrunc, |
| 413 | | .fpext, |
| 414 | 477 | .intcast, |
| 415 | 478 | .trunc, |
| 416 | | .int_from_float, |
| 417 | | .int_from_float_optimized, |
| 418 | | .float_from_int, |
| 419 | 479 | => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { |
| 420 | 480 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 421 | 481 | if (ty_op.ty.toType().isVector(zcu)) { |
| 422 | 482 | continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .ty_op)); |
| 423 | 483 | } |
| 424 | 484 | }, |
| 485 | .abs => { |
| 486 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 487 | switch (l.wantScalarizeOrSoftFloat(.abs, ty_op.ty.toType())) { |
| 488 | .none => {}, |
| 489 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .ty_op)), |
| 490 | .soft_float => continue :inst try l.compilerRtCall( |
| 491 | inst, |
| 492 | softFloatFunc(.abs, ty_op.ty.toType(), zcu), |
| 493 | &.{ty_op.operand}, |
| 494 | ty_op.ty.toType(), |
| 495 | ), |
| 496 | } |
| 497 | }, |
| 498 | .fptrunc => { |
| 499 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 500 | const src_ty = l.typeOf(ty_op.operand); |
| 501 | const dest_ty = ty_op.ty.toType(); |
| 502 | if (src_ty.zigTypeTag(zcu) == .vector) { |
| 503 | if (l.features.has(.scalarize_fptrunc) or |
| 504 | l.wantSoftFloatScalar(src_ty.childType(zcu)) or |
| 505 | l.wantSoftFloatScalar(dest_ty.childType(zcu))) |
| 506 | { |
| 507 | continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .ty_op)); |
| 508 | } |
| 509 | } else if (l.wantSoftFloatScalar(src_ty) or l.wantSoftFloatScalar(dest_ty)) { |
| 510 | continue :inst try l.compilerRtCall(inst, l.softFptruncFunc(src_ty, dest_ty), &.{ty_op.operand}, dest_ty); |
| 511 | } |
| 512 | }, |
| 513 | .fpext => { |
| 514 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 515 | const src_ty = l.typeOf(ty_op.operand); |
| 516 | const dest_ty = ty_op.ty.toType(); |
| 517 | if (src_ty.zigTypeTag(zcu) == .vector) { |
| 518 | if (l.features.has(.scalarize_fpext) or |
| 519 | l.wantSoftFloatScalar(src_ty.childType(zcu)) or |
| 520 | l.wantSoftFloatScalar(dest_ty.childType(zcu))) |
| 521 | { |
| 522 | continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .ty_op)); |
| 523 | } |
| 524 | } else if (l.wantSoftFloatScalar(src_ty) or l.wantSoftFloatScalar(dest_ty)) { |
| 525 | continue :inst try l.compilerRtCall(inst, l.softFpextFunc(src_ty, dest_ty), &.{ty_op.operand}, dest_ty); |
| 526 | } |
| 527 | }, |
| 528 | inline .int_from_float, .int_from_float_optimized => |air_tag| { |
| 529 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 530 | switch (l.wantScalarizeOrSoftFloat(air_tag, l.typeOf(ty_op.operand))) { |
| 531 | .none => {}, |
| 532 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .ty_op)), |
| 533 | .soft_float => switch (try l.softIntFromFloat(inst)) { |
| 534 | .call => |func| continue :inst try l.compilerRtCall(inst, func, &.{ty_op.operand}, ty_op.ty.toType()), |
| 535 | .block_payload => |data| continue :inst l.replaceInst(inst, .block, data), |
| 536 | }, |
| 537 | } |
| 538 | }, |
| 539 | .float_from_int => { |
| 540 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 541 | const dest_ty = ty_op.ty.toType(); |
| 542 | switch (l.wantScalarizeOrSoftFloat(.float_from_int, dest_ty)) { |
| 543 | .none => {}, |
| 544 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .ty_op)), |
| 545 | .soft_float => switch (try l.softFloatFromInt(inst)) { |
| 546 | .call => |func| continue :inst try l.compilerRtCall(inst, func, &.{ty_op.operand}, dest_ty), |
| 547 | .block_payload => |data| continue :inst l.replaceInst(inst, .block, data), |
| 548 | }, |
| 549 | } |
| 550 | }, |
| 425 | 551 | .bitcast => if (l.features.has(.scalarize_bitcast)) { |
| 426 | 552 | if (try l.scalarizeBitcastBlockPayload(inst)) |payload| { |
| 427 | 553 | continue :inst l.replaceInst(inst, .block, payload); |
| ... | ... | @@ -436,22 +562,25 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 436 | 562 | continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .ty_op)); |
| 437 | 563 | } |
| 438 | 564 | }, |
| 439 | | .int_from_float_safe => if (l.features.has(.expand_int_from_float_safe)) { |
| 440 | | assert(!l.features.has(.scalarize_int_from_float_safe)); |
| 441 | | continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, false)); |
| 442 | | } else if (l.features.has(.scalarize_int_from_float_safe)) { |
| 443 | | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 444 | | if (ty_op.ty.toType().isVector(zcu)) { |
| 445 | | continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .ty_op)); |
| 565 | inline .int_from_float_safe, |
| 566 | .int_from_float_optimized_safe, |
| 567 | => |air_tag| { |
| 568 | const optimized = air_tag == .int_from_float_optimized_safe; |
| 569 | const expand_feature = switch (air_tag) { |
| 570 | .int_from_float_safe => .expand_int_from_float_safe, |
| 571 | .int_from_float_optimized_safe => .expand_int_from_float_optimized_safe, |
| 572 | else => unreachable, |
| 573 | }; |
| 574 | if (l.features.has(expand_feature)) { |
| 575 | assert(!l.features.has(.scalarize(air_tag))); |
| 576 | continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, optimized)); |
| 446 | 577 | } |
| 447 | | }, |
| 448 | | .int_from_float_optimized_safe => if (l.features.has(.expand_int_from_float_optimized_safe)) { |
| 449 | | assert(!l.features.has(.scalarize_int_from_float_optimized_safe)); |
| 450 | | continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, true)); |
| 451 | | } else if (l.features.has(.scalarize_int_from_float_optimized_safe)) { |
| 452 | 578 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 453 | | if (ty_op.ty.toType().isVector(zcu)) { |
| 454 | | continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .ty_op)); |
| 579 | switch (l.wantScalarizeOrSoftFloat(air_tag, l.typeOf(ty_op.operand))) { |
| 580 | .none => {}, |
| 581 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .ty_op)), |
| 582 | // Expand the safety check so that soft-float can rewrite the unchecked operation. |
| 583 | .soft_float => continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, optimized)), |
| 455 | 584 | } |
| 456 | 585 | }, |
| 457 | 586 | .block, .loop => { |
| ... | ... | @@ -483,12 +612,26 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 483 | 612 | .ceil, |
| 484 | 613 | .round, |
| 485 | 614 | .trunc_float, |
| 486 | | .neg, |
| 487 | | .neg_optimized, |
| 488 | | => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { |
| 489 | | const un_op = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op; |
| 490 | | if (l.typeOf(un_op).isVector(zcu)) { |
| 491 | | continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .un_op)); |
| 615 | => |air_tag| { |
| 616 | const operand = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op; |
| 617 | const ty = l.typeOf(operand); |
| 618 | switch (l.wantScalarizeOrSoftFloat(air_tag, ty)) { |
| 619 | .none => {}, |
| 620 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .un_op)), |
| 621 | .soft_float => continue :inst try l.compilerRtCall( |
| 622 | inst, |
| 623 | softFloatFunc(air_tag, ty, zcu), |
| 624 | &.{operand}, |
| 625 | l.typeOf(operand), |
| 626 | ), |
| 627 | } |
| 628 | }, |
| 629 | inline .neg, .neg_optimized => |air_tag| { |
| 630 | const operand = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op; |
| 631 | switch (l.wantScalarizeOrSoftFloat(air_tag, l.typeOf(operand))) { |
| 632 | .none => {}, |
| 633 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .un_op)), |
| 634 | .soft_float => continue :inst l.replaceInst(inst, .block, try l.softFloatNegBlockPayload(inst, operand)), |
| 492 | 635 | } |
| 493 | 636 | }, |
| 494 | 637 | .cmp_lt, |
| ... | ... | @@ -503,11 +646,24 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 503 | 646 | .cmp_gt_optimized, |
| 504 | 647 | .cmp_neq, |
| 505 | 648 | .cmp_neq_optimized, |
| 506 | | => {}, |
| 507 | | inline .cmp_vector, .cmp_vector_optimized => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { |
| 649 | => |air_tag| { |
| 650 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 651 | const ty = l.typeOf(bin_op.lhs); |
| 652 | if (l.wantSoftFloatScalar(ty)) { |
| 653 | continue :inst l.replaceInst( |
| 654 | inst, |
| 655 | .block, |
| 656 | try l.softFloatCmpBlockPayload(inst, ty, air_tag.toCmpOp().?, bin_op.lhs, bin_op.rhs), |
| 657 | ); |
| 658 | } |
| 659 | }, |
| 660 | inline .cmp_vector, .cmp_vector_optimized => |air_tag| { |
| 508 | 661 | const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 509 | | if (ty_pl.ty.toType().isVector(zcu)) { |
| 510 | | continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .cmp_vector)); |
| 662 | const payload = l.extraData(Air.VectorCmp, ty_pl.payload).data; |
| 663 | switch (l.wantScalarizeOrSoftFloat(air_tag, l.typeOf(payload.lhs))) { |
| 664 | .none => {}, |
| 665 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .cmp_vector)), |
| 666 | .soft_float => unreachable, // the operand is not a scalar |
| 511 | 667 | } |
| 512 | 668 | }, |
| 513 | 669 | .cond_br => { |
| ... | ... | @@ -615,16 +771,27 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 615 | 771 | .ptr_elem_ptr, |
| 616 | 772 | .array_to_slice, |
| 617 | 773 | => {}, |
| 618 | | .reduce, .reduce_optimized => if (l.features.has(.reduce_one_elem_to_bitcast)) { |
| 774 | inline .reduce, .reduce_optimized => |air_tag| { |
| 619 | 775 | const reduce = l.air_instructions.items(.data)[@intFromEnum(inst)].reduce; |
| 620 | 776 | const vector_ty = l.typeOf(reduce.operand); |
| 621 | | switch (vector_ty.vectorLen(zcu)) { |
| 622 | | 0 => unreachable, |
| 623 | | 1 => continue :inst l.replaceInst(inst, .bitcast, .{ .ty_op = .{ |
| 624 | | .ty = .fromType(vector_ty.childType(zcu)), |
| 625 | | .operand = reduce.operand, |
| 626 | | } }), |
| 627 | | else => {}, |
| 777 | if (l.features.has(.reduce_one_elem_to_bitcast)) { |
| 778 | switch (vector_ty.vectorLen(zcu)) { |
| 779 | 0 => unreachable, |
| 780 | 1 => continue :inst l.replaceInst(inst, .bitcast, .{ .ty_op = .{ |
| 781 | .ty = .fromType(vector_ty.childType(zcu)), |
| 782 | .operand = reduce.operand, |
| 783 | } }), |
| 784 | else => {}, |
| 785 | } |
| 786 | } |
| 787 | switch (l.wantScalarizeOrSoftFloat(air_tag, vector_ty)) { |
| 788 | .none => {}, |
| 789 | .scalarize => continue :inst l.replaceInst( |
| 790 | inst, |
| 791 | .block, |
| 792 | try l.scalarizeReduceBlockPayload(inst, air_tag == .reduce_optimized), |
| 793 | ), |
| 794 | .soft_float => unreachable, // the operand is not a scalar |
| 628 | 795 | } |
| 629 | 796 | }, |
| 630 | 797 | .splat => if (l.features.has(.splat_one_elem_to_bitcast)) { |
| ... | ... | @@ -638,14 +805,30 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 638 | 805 | else => {}, |
| 639 | 806 | } |
| 640 | 807 | }, |
| 641 | | .shuffle_one => if (l.features.has(.scalarize_shuffle_one)) { |
| 642 | | continue :inst l.replaceInst(inst, .block, try l.scalarizeShuffleOneBlockPayload(inst)); |
| 808 | .shuffle_one => { |
| 809 | const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 810 | switch (l.wantScalarizeOrSoftFloat(.shuffle_one, ty_pl.ty.toType())) { |
| 811 | .none => {}, |
| 812 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeShuffleOneBlockPayload(inst)), |
| 813 | .soft_float => unreachable, // the operand is not a scalar |
| 814 | } |
| 643 | 815 | }, |
| 644 | | .shuffle_two => if (l.features.has(.scalarize_shuffle_two)) { |
| 645 | | continue :inst l.replaceInst(inst, .block, try l.scalarizeShuffleTwoBlockPayload(inst)); |
| 816 | .shuffle_two => { |
| 817 | const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 818 | switch (l.wantScalarizeOrSoftFloat(.shuffle_two, ty_pl.ty.toType())) { |
| 819 | .none => {}, |
| 820 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeShuffleTwoBlockPayload(inst)), |
| 821 | .soft_float => unreachable, // the operand is not a scalar |
| 822 | } |
| 646 | 823 | }, |
| 647 | | .select => if (l.features.has(.scalarize_select)) { |
| 648 | | continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .select)); |
| 824 | .select => { |
| 825 | const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op; |
| 826 | const bin = l.extraData(Air.Bin, pl_op.payload).data; |
| 827 | switch (l.wantScalarizeOrSoftFloat(.select, l.typeOf(bin.lhs))) { |
| 828 | .none => {}, |
| 829 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .select)), |
| 830 | .soft_float => unreachable, // the operand is not a scalar |
| 831 | } |
| 649 | 832 | }, |
| 650 | 833 | .memset, |
| 651 | 834 | .memset_safe, |
| ... | ... | @@ -685,10 +868,17 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 685 | 868 | } |
| 686 | 869 | }, |
| 687 | 870 | .union_init, .prefetch => {}, |
| 688 | | .mul_add => if (l.features.has(.scalarize_mul_add)) { |
| 871 | .mul_add => { |
| 689 | 872 | const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op; |
| 690 | | if (l.typeOf(pl_op.operand).isVector(zcu)) { |
| 691 | | continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .pl_op_bin)); |
| 873 | const ty = l.typeOf(pl_op.operand); |
| 874 | switch (l.wantScalarizeOrSoftFloat(.mul_add, ty)) { |
| 875 | .none => {}, |
| 876 | .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .pl_op_bin)), |
| 877 | .soft_float => { |
| 878 | const bin = l.extraData(Air.Bin, pl_op.payload).data; |
| 879 | const func = softFloatFunc(.mul_add, ty, zcu); |
| 880 | continue :inst try l.compilerRtCall(inst, func, &.{ bin.lhs, bin.rhs, pl_op.operand }, ty); |
| 881 | }, |
| 692 | 882 | } |
| 693 | 883 | }, |
| 694 | 884 | .field_parent_ptr, |
| ... | ... | @@ -709,6 +899,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 709 | 899 | .work_group_id, |
| 710 | 900 | .legalize_vec_elem_val, |
| 711 | 901 | .legalize_vec_store_elem, |
| 902 | .legalize_compiler_rt_call, |
| 712 | 903 | => {}, |
| 713 | 904 | } |
| 714 | 905 | } |
| ... | ... | @@ -1606,6 +1797,128 @@ fn scalarizeOverflowBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error! |
| 1606 | 1797 | .payload = try l.addBlockBody(main_block.body()), |
| 1607 | 1798 | } }; |
| 1608 | 1799 | } |
| 1800 | fn scalarizeReduceBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, optimized: bool) Error!Air.Inst.Data { |
| 1801 | const pt = l.pt; |
| 1802 | const zcu = pt.zcu; |
| 1803 | |
| 1804 | const reduce = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].reduce; |
| 1805 | |
| 1806 | const vector_ty = l.typeOf(reduce.operand); |
| 1807 | const scalar_ty = vector_ty.childType(zcu); |
| 1808 | |
| 1809 | const ident_val: Value = switch (reduce.operation) { |
| 1810 | // identity for add is 0; identity for OR and XOR is all 0 bits |
| 1811 | .Or, .Xor, .Add => switch (scalar_ty.zigTypeTag(zcu)) { |
| 1812 | .int => try pt.intValue(scalar_ty, 0), |
| 1813 | .float => try pt.floatValue(scalar_ty, 0.0), |
| 1814 | else => unreachable, |
| 1815 | }, |
| 1816 | // identity for multiplication is 1 |
| 1817 | .Mul => switch (scalar_ty.zigTypeTag(zcu)) { |
| 1818 | .int => try pt.intValue(scalar_ty, 1), |
| 1819 | .float => try pt.floatValue(scalar_ty, 1.0), |
| 1820 | else => unreachable, |
| 1821 | }, |
| 1822 | // identity for AND is all 1 bits |
| 1823 | .And => switch (scalar_ty.intInfo(zcu).signedness) { |
| 1824 | .unsigned => try scalar_ty.maxIntScalar(pt, scalar_ty), |
| 1825 | .signed => try pt.intValue(scalar_ty, -1), |
| 1826 | }, |
| 1827 | // identity for @min is maximum value |
| 1828 | .Min => switch (scalar_ty.zigTypeTag(zcu)) { |
| 1829 | .int => try scalar_ty.maxIntScalar(pt, scalar_ty), |
| 1830 | .float => try pt.floatValue(scalar_ty, std.math.inf(f32)), |
| 1831 | else => unreachable, |
| 1832 | }, |
| 1833 | // identity for @max is minimum value |
| 1834 | .Max => switch (scalar_ty.zigTypeTag(zcu)) { |
| 1835 | .int => try scalar_ty.minIntScalar(pt, scalar_ty), |
| 1836 | .float => try pt.floatValue(scalar_ty, -std.math.inf(f32)), |
| 1837 | else => unreachable, |
| 1838 | }, |
| 1839 | }; |
| 1840 | |
| 1841 | const op_tag: Air.Inst.Tag = switch (reduce.operation) { |
| 1842 | .Or => .bit_or, |
| 1843 | .And => .bit_and, |
| 1844 | .Xor => .xor, |
| 1845 | .Min => .min, |
| 1846 | .Max => .max, |
| 1847 | .Add => switch (scalar_ty.zigTypeTag(zcu)) { |
| 1848 | .int => .add_wrap, |
| 1849 | .float => if (optimized) .add_optimized else .add, |
| 1850 | else => unreachable, |
| 1851 | }, |
| 1852 | .Mul => switch (scalar_ty.zigTypeTag(zcu)) { |
| 1853 | .int => .mul_wrap, |
| 1854 | .float => if (optimized) .mul_optimized else .mul, |
| 1855 | else => unreachable, |
| 1856 | }, |
| 1857 | }; |
| 1858 | |
| 1859 | // %1 = block(Scalar, { |
| 1860 | // %2 = alloc(*usize) |
| 1861 | // %3 = alloc(*Scalar) |
| 1862 | // %4 = store(%2, @zero_usize) |
| 1863 | // %5 = store(%3, <Scalar, 0>) // or whatever the identity is for this operator |
| 1864 | // %6 = loop({ |
| 1865 | // %7 = load(%2) |
| 1866 | // %8 = legalize_vec_elem_val(orig_operand, %7) |
| 1867 | // %9 = load(%3) |
| 1868 | // %10 = add(%8, %9) // or whatever the operator is |
| 1869 | // %11 = cmp_eq(%7, <usize, N-1>) |
| 1870 | // %12 = cond_br(%11, { |
| 1871 | // %13 = br(%1, %10) |
| 1872 | // }, { |
| 1873 | // %14 = store(%3, %10) |
| 1874 | // %15 = add(%7, @one_usize) |
| 1875 | // %16 = store(%2, %15) |
| 1876 | // %17 = repeat(%6) |
| 1877 | // }) |
| 1878 | // }) |
| 1879 | // }) |
| 1880 | |
| 1881 | var inst_buf: [16]Air.Inst.Index = undefined; |
| 1882 | var main_block: Block = .init(&inst_buf); |
| 1883 | try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len); |
| 1884 | |
| 1885 | const index_ptr = main_block.addTy(l, .alloc, .ptr_usize).toRef(); |
| 1886 | const accum_ptr = main_block.addTy(l, .alloc, try pt.singleMutPtrType(scalar_ty)).toRef(); |
| 1887 | _ = main_block.addBinOp(l, .store, index_ptr, .zero_usize); |
| 1888 | _ = main_block.addBinOp(l, .store, accum_ptr, .fromValue(ident_val)); |
| 1889 | |
| 1890 | var loop: Loop = .init(l, &main_block); |
| 1891 | loop.block = .init(main_block.stealRemainingCapacity()); |
| 1892 | |
| 1893 | const index_val = loop.block.addTyOp(l, .load, .usize, index_ptr).toRef(); |
| 1894 | const elem_val = loop.block.addBinOp(l, .legalize_vec_elem_val, reduce.operand, index_val).toRef(); |
| 1895 | const old_accum = loop.block.addTyOp(l, .load, scalar_ty, accum_ptr).toRef(); |
| 1896 | const new_accum = loop.block.addBinOp(l, op_tag, old_accum, elem_val).toRef(); |
| 1897 | |
| 1898 | const is_end_val = loop.block.addBinOp(l, .cmp_eq, index_val, .fromValue(try pt.intValue(.usize, vector_ty.vectorLen(zcu) - 1))).toRef(); |
| 1899 | |
| 1900 | var condbr: CondBr = .init(l, is_end_val, &loop.block, .{}); |
| 1901 | |
| 1902 | condbr.then_block = .init(loop.block.stealRemainingCapacity()); |
| 1903 | condbr.then_block.addBr(l, orig_inst, new_accum); |
| 1904 | |
| 1905 | condbr.else_block = .init(condbr.then_block.stealRemainingCapacity()); |
| 1906 | _ = condbr.else_block.addBinOp(l, .store, accum_ptr, new_accum); |
| 1907 | const new_index_val = condbr.else_block.addBinOp(l, .add, index_val, .one_usize).toRef(); |
| 1908 | _ = condbr.else_block.addBinOp(l, .store, index_ptr, new_index_val); |
| 1909 | _ = condbr.else_block.add(l, .{ |
| 1910 | .tag = .repeat, |
| 1911 | .data = .{ .repeat = .{ .loop_inst = loop.inst } }, |
| 1912 | }); |
| 1913 | |
| 1914 | try condbr.finish(l); |
| 1915 | try loop.finish(l); |
| 1916 | |
| 1917 | return .{ .ty_pl = .{ |
| 1918 | .ty = .fromType(scalar_ty), |
| 1919 | .payload = try l.addBlockBody(main_block.body()), |
| 1920 | } }; |
| 1921 | } |
| 1609 | 1922 | |
| 1610 | 1923 | fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.Inst.Data { |
| 1611 | 1924 | const pt = l.pt; |
| ... | ... | @@ -2298,6 +2611,22 @@ const Block = struct { |
| 2298 | 2611 | }); |
| 2299 | 2612 | } |
| 2300 | 2613 | |
| 2614 | fn addCompilerRtCall(b: *Block, l: *Legalize, func: Air.CompilerRtFunc, args: []const Air.Inst.Ref) Error!Air.Inst.Index { |
| 2615 | return b.add(l, .{ |
| 2616 | .tag = .legalize_compiler_rt_call, |
| 2617 | .data = .{ .legalize_compiler_rt_call = .{ |
| 2618 | .func = func, |
| 2619 | .payload = payload: { |
| 2620 | const extra_len = @typeInfo(Air.Call).@"struct".fields.len + args.len; |
| 2621 | try l.air_extra.ensureUnusedCapacity(l.pt.zcu.gpa, extra_len); |
| 2622 | const index = l.addExtra(Air.Call, .{ .args_len = @intCast(args.len) }) catch unreachable; |
| 2623 | l.air_extra.appendSliceAssumeCapacity(@ptrCast(args)); |
| 2624 | break :payload index; |
| 2625 | }, |
| 2626 | } }, |
| 2627 | }); |
| 2628 | } |
| 2629 | |
| 2301 | 2630 | /// Adds the code to call the panic handler `panic_id`. This is usually `.call` then `.unreach`, |
| 2302 | 2631 | /// but if `Zcu.Feature.panic_fn` is unsupported, we lower to `.trap` instead. |
| 2303 | 2632 | fn addPanic(b: *Block, l: *Legalize, panic_id: Zcu.SimplePanicId) Error!void { |
| ... | ... | @@ -2365,14 +2694,7 @@ const Block = struct { |
| 2365 | 2694 | optimized: bool, |
| 2366 | 2695 | ) Air.Inst.Index { |
| 2367 | 2696 | return b.add(l, .{ |
| 2368 | | .tag = switch (op) { |
| 2369 | | .lt => if (optimized) .cmp_lt_optimized else .cmp_lt, |
| 2370 | | .lte => if (optimized) .cmp_lte_optimized else .cmp_lte, |
| 2371 | | .eq => if (optimized) .cmp_eq_optimized else .cmp_eq, |
| 2372 | | .gte => if (optimized) .cmp_gte_optimized else .cmp_gte, |
| 2373 | | .gt => if (optimized) .cmp_gt_optimized else .cmp_gt, |
| 2374 | | .neq => if (optimized) .cmp_neq_optimized else .cmp_neq, |
| 2375 | | }, |
| 2697 | .tag = .fromCmpOp(op, optimized), |
| 2376 | 2698 | .data = .{ .bin_op = .{ |
| 2377 | 2699 | .lhs = lhs, |
| 2378 | 2700 | .rhs = rhs, |
| ... | ... | @@ -2399,6 +2721,82 @@ const Block = struct { |
| 2399 | 2721 | return operand; |
| 2400 | 2722 | } |
| 2401 | 2723 | |
| 2724 | /// This function emits *two* instructions. |
| 2725 | fn addSoftFloatCmp( |
| 2726 | b: *Block, |
| 2727 | l: *Legalize, |
| 2728 | float_ty: Type, |
| 2729 | op: std.math.CompareOperator, |
| 2730 | lhs: Air.Inst.Ref, |
| 2731 | rhs: Air.Inst.Ref, |
| 2732 | ) Error!Air.Inst.Ref { |
| 2733 | const pt = l.pt; |
| 2734 | const target = pt.zcu.getTarget(); |
| 2735 | const use_aeabi = target.cpu.arch.isArm() and switch (target.abi) { |
| 2736 | .eabi, |
| 2737 | .eabihf, |
| 2738 | .musleabi, |
| 2739 | .musleabihf, |
| 2740 | .gnueabi, |
| 2741 | .gnueabihf, |
| 2742 | .android, |
| 2743 | .androideabi, |
| 2744 | => true, |
| 2745 | else => false, |
| 2746 | }; |
| 2747 | const func: Air.CompilerRtFunc, const ret_cmp_op: std.math.CompareOperator = switch (float_ty.floatBits(target)) { |
| 2748 | // zig fmt: off |
| 2749 | 16 => switch (op) { |
| 2750 | .eq => .{ .__eqhf2, .eq }, |
| 2751 | .neq => .{ .__nehf2, .neq }, |
| 2752 | .lt => .{ .__lthf2, .lt }, |
| 2753 | .lte => .{ .__lehf2, .lte }, |
| 2754 | .gt => .{ .__gthf2, .gt }, |
| 2755 | .gte => .{ .__gehf2, .gte }, |
| 2756 | }, |
| 2757 | 32 => switch (op) { |
| 2758 | .eq => if (use_aeabi) .{ .__aeabi_fcmpeq, .neq } else .{ .__eqsf2, .eq }, |
| 2759 | .neq => if (use_aeabi) .{ .__aeabi_fcmpeq, .eq } else .{ .__nesf2, .neq }, |
| 2760 | .lt => if (use_aeabi) .{ .__aeabi_fcmplt, .neq } else .{ .__ltsf2, .lt }, |
| 2761 | .lte => if (use_aeabi) .{ .__aeabi_fcmple, .neq } else .{ .__lesf2, .lte }, |
| 2762 | .gt => if (use_aeabi) .{ .__aeabi_fcmpgt, .neq } else .{ .__gtsf2, .gt }, |
| 2763 | .gte => if (use_aeabi) .{ .__aeabi_fcmpge, .neq } else .{ .__gesf2, .gte }, |
| 2764 | }, |
| 2765 | 64 => switch (op) { |
| 2766 | .eq => if (use_aeabi) .{ .__aeabi_dcmpeq, .neq } else .{ .__eqdf2, .eq }, |
| 2767 | .neq => if (use_aeabi) .{ .__aeabi_dcmpeq, .eq } else .{ .__nedf2, .neq }, |
| 2768 | .lt => if (use_aeabi) .{ .__aeabi_dcmplt, .neq } else .{ .__ltdf2, .lt }, |
| 2769 | .lte => if (use_aeabi) .{ .__aeabi_dcmple, .neq } else .{ .__ledf2, .lte }, |
| 2770 | .gt => if (use_aeabi) .{ .__aeabi_dcmpgt, .neq } else .{ .__gtdf2, .gt }, |
| 2771 | .gte => if (use_aeabi) .{ .__aeabi_dcmpge, .neq } else .{ .__gedf2, .gte }, |
| 2772 | }, |
| 2773 | 80 => switch (op) { |
| 2774 | .eq => .{ .__eqxf2, .eq }, |
| 2775 | .neq => .{ .__nexf2, .neq }, |
| 2776 | .lt => .{ .__ltxf2, .lt }, |
| 2777 | .lte => .{ .__lexf2, .lte }, |
| 2778 | .gt => .{ .__gtxf2, .gt }, |
| 2779 | .gte => .{ .__gexf2, .gte }, |
| 2780 | }, |
| 2781 | 128 => switch (op) { |
| 2782 | .eq => .{ .__eqtf2, .eq }, |
| 2783 | .neq => .{ .__netf2, .neq }, |
| 2784 | .lt => .{ .__lttf2, .lt }, |
| 2785 | .lte => .{ .__letf2, .lte }, |
| 2786 | .gt => .{ .__gttf2, .gt }, |
| 2787 | .gte => .{ .__getf2, .gte }, |
| 2788 | }, |
| 2789 | else => unreachable, |
| 2790 | // zig fmt: on |
| 2791 | }; |
| 2792 | const call_inst = try b.addCompilerRtCall(l, func, &.{ lhs, rhs }); |
| 2793 | const raw_result = call_inst.toRef(); |
| 2794 | assert(l.typeOf(raw_result).toIntern() == .i32_type); |
| 2795 | const zero_i32: Air.Inst.Ref = .fromValue(try pt.intValue(.i32, 0)); |
| 2796 | const ret_cmp_tag: Air.Inst.Tag = .fromCmpOp(ret_cmp_op, false); |
| 2797 | return b.addBinOp(l, ret_cmp_tag, raw_result, zero_i32).toRef(); |
| 2798 | } |
| 2799 | |
| 2402 | 2800 | /// Returns the unused capacity of `b.instructions`, and shrinks `b.instructions` down to `b.len`. |
| 2403 | 2801 | /// This is useful when you've provided a buffer big enough for all your instructions, but you are |
| 2404 | 2802 | /// now starting a new block and some of them need to live there instead. |
| ... | ... | @@ -2525,6 +2923,484 @@ inline fn replaceInst(l: *Legalize, inst: Air.Inst.Index, comptime tag: Air.Inst |
| 2525 | 2923 | return tag; |
| 2526 | 2924 | } |
| 2527 | 2925 | |
| 2926 | fn compilerRtCall( |
| 2927 | l: *Legalize, |
| 2928 | orig_inst: Air.Inst.Index, |
| 2929 | func: Air.CompilerRtFunc, |
| 2930 | args: []const Air.Inst.Ref, |
| 2931 | result_ty: Type, |
| 2932 | ) Error!Air.Inst.Tag { |
| 2933 | const zcu = l.pt.zcu; |
| 2934 | const gpa = zcu.gpa; |
| 2935 | |
| 2936 | const func_ret_ty = func.returnType(); |
| 2937 | |
| 2938 | if (func_ret_ty.toIntern() == result_ty.toIntern()) { |
| 2939 | try l.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).@"struct".fields.len + args.len); |
| 2940 | const payload = l.addExtra(Air.Call, .{ .args_len = @intCast(args.len) }) catch unreachable; |
| 2941 | l.air_extra.appendSliceAssumeCapacity(@ptrCast(args)); |
| 2942 | return l.replaceInst(orig_inst, .legalize_compiler_rt_call, .{ .legalize_compiler_rt_call = .{ |
| 2943 | .func = func, |
| 2944 | .payload = payload, |
| 2945 | } }); |
| 2946 | } |
| 2947 | |
| 2948 | // We need to bitcast the result to an "alias" type (e.g. c_int/i32, c_longdouble/f128). |
| 2949 | |
| 2950 | assert(func_ret_ty.bitSize(zcu) == result_ty.bitSize(zcu)); |
| 2951 | |
| 2952 | var inst_buf: [3]Air.Inst.Index = undefined; |
| 2953 | var main_block: Block = .init(&inst_buf); |
| 2954 | try l.air_instructions.ensureUnusedCapacity(gpa, inst_buf.len); |
| 2955 | |
| 2956 | const call_inst = try main_block.addCompilerRtCall(l, func, args); |
| 2957 | const casted_result = main_block.addBitCast(l, result_ty, call_inst.toRef()); |
| 2958 | main_block.addBr(l, orig_inst, casted_result); |
| 2959 | |
| 2960 | return l.replaceInst(orig_inst, .block, .{ .ty_pl = .{ |
| 2961 | .ty = .fromType(result_ty), |
| 2962 | .payload = try l.addBlockBody(main_block.body()), |
| 2963 | } }); |
| 2964 | } |
| 2965 | |
| 2966 | fn softFptruncFunc(l: *const Legalize, src_ty: Type, dst_ty: Type) Air.CompilerRtFunc { |
| 2967 | const target = l.pt.zcu.getTarget(); |
| 2968 | const src_bits = src_ty.floatBits(target); |
| 2969 | const dst_bits = dst_ty.floatBits(target); |
| 2970 | assert(dst_bits < src_bits); |
| 2971 | const to_f16_func: Air.CompilerRtFunc = switch (src_bits) { |
| 2972 | 128 => .__trunctfhf2, |
| 2973 | 80 => .__truncxfhf2, |
| 2974 | 64 => .__truncdfhf2, |
| 2975 | 32 => .__truncsfhf2, |
| 2976 | else => unreachable, |
| 2977 | }; |
| 2978 | const offset: u8 = switch (dst_bits) { |
| 2979 | 16 => 0, |
| 2980 | 32 => 1, |
| 2981 | 64 => 2, |
| 2982 | 80 => 3, |
| 2983 | else => unreachable, |
| 2984 | }; |
| 2985 | return @enumFromInt(@intFromEnum(to_f16_func) + offset); |
| 2986 | } |
| 2987 | fn softFpextFunc(l: *const Legalize, src_ty: Type, dst_ty: Type) Air.CompilerRtFunc { |
| 2988 | const target = l.pt.zcu.getTarget(); |
| 2989 | const src_bits = src_ty.floatBits(target); |
| 2990 | const dst_bits = dst_ty.floatBits(target); |
| 2991 | assert(dst_bits > src_bits); |
| 2992 | const to_f128_func: Air.CompilerRtFunc = switch (src_bits) { |
| 2993 | 16 => .__extendhftf2, |
| 2994 | 32 => .__extendsftf2, |
| 2995 | 64 => .__extenddftf2, |
| 2996 | 80 => .__extendxftf2, |
| 2997 | else => unreachable, |
| 2998 | }; |
| 2999 | const offset: u8 = switch (dst_bits) { |
| 3000 | 128 => 0, |
| 3001 | 80 => 1, |
| 3002 | 64 => 2, |
| 3003 | 32 => 3, |
| 3004 | else => unreachable, |
| 3005 | }; |
| 3006 | return @enumFromInt(@intFromEnum(to_f128_func) + offset); |
| 3007 | } |
| 3008 | fn softFloatFromInt(l: *Legalize, orig_inst: Air.Inst.Index) Error!union(enum) { |
| 3009 | call: Air.CompilerRtFunc, |
| 3010 | block_payload: Air.Inst.Data, |
| 3011 | } { |
| 3012 | const pt = l.pt; |
| 3013 | const zcu = pt.zcu; |
| 3014 | const target = zcu.getTarget(); |
| 3015 | |
| 3016 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op; |
| 3017 | const dest_ty = ty_op.ty.toType(); |
| 3018 | const src_ty = l.typeOf(ty_op.operand); |
| 3019 | |
| 3020 | const src_info = src_ty.intInfo(zcu); |
| 3021 | const float_off: u32 = switch (dest_ty.floatBits(target)) { |
| 3022 | 16 => 0, |
| 3023 | 32 => 1, |
| 3024 | 64 => 2, |
| 3025 | 80 => 3, |
| 3026 | 128 => 4, |
| 3027 | else => unreachable, |
| 3028 | }; |
| 3029 | const base: Air.CompilerRtFunc = switch (src_info.signedness) { |
| 3030 | .signed => .__floatsihf, |
| 3031 | .unsigned => .__floatunsihf, |
| 3032 | }; |
| 3033 | fixed: { |
| 3034 | const extended_int_bits: u16, const int_bits_off: u32 = switch (src_info.bits) { |
| 3035 | 0...32 => .{ 32, 0 }, |
| 3036 | 33...64 => .{ 64, 5 }, |
| 3037 | 65...128 => .{ 128, 10 }, |
| 3038 | else => break :fixed, |
| 3039 | }; |
| 3040 | // x86_64-windows uses an odd callconv for 128-bit integers, so we use the |
| 3041 | // arbitrary-precision routine in that case for simplicity. |
| 3042 | if (target.cpu.arch == .x86_64 and target.os.tag == .windows and extended_int_bits == 128) { |
| 3043 | break :fixed; |
| 3044 | } |
| 3045 | |
| 3046 | const func: Air.CompilerRtFunc = @enumFromInt(@intFromEnum(base) + int_bits_off + float_off); |
| 3047 | if (extended_int_bits == src_info.bits) return .{ .call = func }; |
| 3048 | |
| 3049 | // We need to emit a block which first sign/zero-extends to the right type and *then* calls |
| 3050 | // the required routine. |
| 3051 | const extended_ty = try l.pt.intType(src_info.signedness, extended_int_bits); |
| 3052 | |
| 3053 | var inst_buf: [4]Air.Inst.Index = undefined; |
| 3054 | var main_block: Block = .init(&inst_buf); |
| 3055 | try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len); |
| 3056 | |
| 3057 | const extended_val = main_block.addTyOp(l, .intcast, extended_ty, ty_op.operand).toRef(); |
| 3058 | const call_inst = try main_block.addCompilerRtCall(l, func, &.{extended_val}); |
| 3059 | const casted_result = main_block.addBitCast(l, dest_ty, call_inst.toRef()); |
| 3060 | main_block.addBr(l, orig_inst, casted_result); |
| 3061 | |
| 3062 | return .{ .block_payload = .{ .ty_pl = .{ |
| 3063 | .ty = .fromType(dest_ty), |
| 3064 | .payload = try l.addBlockBody(main_block.body()), |
| 3065 | } } }; |
| 3066 | } |
| 3067 | |
| 3068 | // We need to emit a block which puts the integer into an `alloc` (possibly sign/zero-extended) |
| 3069 | // and calls an arbitrary-width conversion routine. |
| 3070 | |
| 3071 | const func: Air.CompilerRtFunc = @enumFromInt(@intFromEnum(base) + 15 + float_off); |
| 3072 | |
| 3073 | // The extended integer routines expect the integer representation where the integer is |
| 3074 | // effectively zero- or sign-extended to its ABI size. We represent that by intcasting to |
| 3075 | // such an integer type and passing a pointer to *that*. |
| 3076 | const extended_ty = try pt.intType(src_info.signedness, @intCast(src_ty.abiSize(zcu) * 8)); |
| 3077 | assert(extended_ty.abiSize(zcu) == src_ty.abiSize(zcu)); |
| 3078 | |
| 3079 | var inst_buf: [6]Air.Inst.Index = undefined; |
| 3080 | var main_block: Block = .init(&inst_buf); |
| 3081 | try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len); |
| 3082 | |
| 3083 | const extended_val: Air.Inst.Ref = if (extended_ty.toIntern() != src_ty.toIntern()) ext: { |
| 3084 | break :ext main_block.addTyOp(l, .intcast, extended_ty, ty_op.operand).toRef(); |
| 3085 | } else ext: { |
| 3086 | _ = main_block.stealCapacity(1); |
| 3087 | break :ext ty_op.operand; |
| 3088 | }; |
| 3089 | const extended_ptr = main_block.addTy(l, .alloc, try pt.singleMutPtrType(extended_ty)).toRef(); |
| 3090 | _ = main_block.addBinOp(l, .store, extended_ptr, extended_val); |
| 3091 | const bits_val = try pt.intValue(.usize, src_info.bits); |
| 3092 | const call_inst = try main_block.addCompilerRtCall(l, func, &.{ extended_ptr, .fromValue(bits_val) }); |
| 3093 | const casted_result = main_block.addBitCast(l, dest_ty, call_inst.toRef()); |
| 3094 | main_block.addBr(l, orig_inst, casted_result); |
| 3095 | |
| 3096 | return .{ .block_payload = .{ .ty_pl = .{ |
| 3097 | .ty = .fromType(dest_ty), |
| 3098 | .payload = try l.addBlockBody(main_block.body()), |
| 3099 | } } }; |
| 3100 | } |
| 3101 | fn softIntFromFloat(l: *Legalize, orig_inst: Air.Inst.Index) Error!union(enum) { |
| 3102 | call: Air.CompilerRtFunc, |
| 3103 | block_payload: Air.Inst.Data, |
| 3104 | } { |
| 3105 | const pt = l.pt; |
| 3106 | const zcu = pt.zcu; |
| 3107 | const target = zcu.getTarget(); |
| 3108 | |
| 3109 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op; |
| 3110 | const src_ty = l.typeOf(ty_op.operand); |
| 3111 | const dest_ty = ty_op.ty.toType(); |
| 3112 | |
| 3113 | const dest_info = dest_ty.intInfo(zcu); |
| 3114 | const float_off: u32 = switch (src_ty.floatBits(target)) { |
| 3115 | 16 => 0, |
| 3116 | 32 => 1, |
| 3117 | 64 => 2, |
| 3118 | 80 => 3, |
| 3119 | 128 => 4, |
| 3120 | else => unreachable, |
| 3121 | }; |
| 3122 | const base: Air.CompilerRtFunc = switch (dest_info.signedness) { |
| 3123 | .signed => .__fixhfsi, |
| 3124 | .unsigned => .__fixunshfsi, |
| 3125 | }; |
| 3126 | fixed: { |
| 3127 | const extended_int_bits: u16, const int_bits_off: u32 = switch (dest_info.bits) { |
| 3128 | 0...32 => .{ 32, 0 }, |
| 3129 | 33...64 => .{ 64, 5 }, |
| 3130 | 65...128 => .{ 128, 10 }, |
| 3131 | else => break :fixed, |
| 3132 | }; |
| 3133 | // x86_64-windows uses an odd callconv for 128-bit integers, so we use the |
| 3134 | // arbitrary-precision routine in that case for simplicity. |
| 3135 | if (target.cpu.arch == .x86_64 and target.os.tag == .windows and extended_int_bits == 128) { |
| 3136 | break :fixed; |
| 3137 | } |
| 3138 | |
| 3139 | const func: Air.CompilerRtFunc = @enumFromInt(@intFromEnum(base) + int_bits_off + float_off); |
| 3140 | if (extended_int_bits == dest_info.bits) return .{ .call = func }; |
| 3141 | |
| 3142 | // We need to emit a block which calls the routine and then casts to the required type. |
| 3143 | |
| 3144 | var inst_buf: [3]Air.Inst.Index = undefined; |
| 3145 | var main_block: Block = .init(&inst_buf); |
| 3146 | try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len); |
| 3147 | |
| 3148 | const call_inst = try main_block.addCompilerRtCall(l, func, &.{ty_op.operand}); |
| 3149 | const casted_val = main_block.addTyOp(l, .intcast, dest_ty, call_inst.toRef()).toRef(); |
| 3150 | main_block.addBr(l, orig_inst, casted_val); |
| 3151 | |
| 3152 | return .{ .block_payload = .{ .ty_pl = .{ |
| 3153 | .ty = .fromType(dest_ty), |
| 3154 | .payload = try l.addBlockBody(main_block.body()), |
| 3155 | } } }; |
| 3156 | } |
| 3157 | |
| 3158 | // We need to emit a block which calls an arbitrary-width conversion routine, then loads the |
| 3159 | // integer from an `alloc` and possibly truncates it. |
| 3160 | const func: Air.CompilerRtFunc = @enumFromInt(@intFromEnum(base) + 15 + float_off); |
| 3161 | |
| 3162 | const extended_ty = try pt.intType(dest_info.signedness, @intCast(dest_ty.abiSize(zcu) * 8)); |
| 3163 | assert(extended_ty.abiSize(zcu) == dest_ty.abiSize(zcu)); |
| 3164 | |
| 3165 | var inst_buf: [5]Air.Inst.Index = undefined; |
| 3166 | var main_block: Block = .init(&inst_buf); |
| 3167 | try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len); |
| 3168 | |
| 3169 | const extended_ptr = main_block.addTy(l, .alloc, try pt.singleMutPtrType(extended_ty)).toRef(); |
| 3170 | const bits_val = try pt.intValue(.usize, dest_info.bits); |
| 3171 | _ = try main_block.addCompilerRtCall(l, func, &.{ extended_ptr, .fromValue(bits_val), ty_op.operand }); |
| 3172 | const extended_val = main_block.addTyOp(l, .load, extended_ty, extended_ptr).toRef(); |
| 3173 | const result_val = main_block.addTyOp(l, .intcast, dest_ty, extended_val).toRef(); |
| 3174 | main_block.addBr(l, orig_inst, result_val); |
| 3175 | |
| 3176 | return .{ .block_payload = .{ .ty_pl = .{ |
| 3177 | .ty = .fromType(dest_ty), |
| 3178 | .payload = try l.addBlockBody(main_block.body()), |
| 3179 | } } }; |
| 3180 | } |
| 3181 | fn softFloatFunc(op: Air.Inst.Tag, float_ty: Type, zcu: *const Zcu) Air.CompilerRtFunc { |
| 3182 | const f16_func: Air.CompilerRtFunc = switch (op) { |
| 3183 | .add, .add_optimized => .__addhf3, |
| 3184 | .sub, .sub_optimized => .__subhf3, |
| 3185 | .mul, .mul_optimized => .__mulhf3, |
| 3186 | |
| 3187 | .div_float, |
| 3188 | .div_float_optimized, |
| 3189 | .div_exact, |
| 3190 | .div_exact_optimized, |
| 3191 | => .__divhf3, |
| 3192 | |
| 3193 | .min => .__fminh, |
| 3194 | .max => .__fmaxh, |
| 3195 | |
| 3196 | .ceil => .__ceilh, |
| 3197 | .floor => .__floorh, |
| 3198 | .trunc_float => .__trunch, |
| 3199 | .round => .__roundh, |
| 3200 | |
| 3201 | .log => .__logh, |
| 3202 | .log2 => .__log2h, |
| 3203 | .log10 => .__log10h, |
| 3204 | |
| 3205 | .exp => .__exph, |
| 3206 | .exp2 => .__exp2h, |
| 3207 | |
| 3208 | .sin => .__sinh, |
| 3209 | .cos => .__cosh, |
| 3210 | .tan => .__tanh, |
| 3211 | |
| 3212 | .abs => .__fabsh, |
| 3213 | .sqrt => .__sqrth, |
| 3214 | .rem, .rem_optimized => .__fmodh, |
| 3215 | .mul_add => .__fmah, |
| 3216 | |
| 3217 | else => unreachable, |
| 3218 | }; |
| 3219 | const offset: u8 = switch (float_ty.floatBits(zcu.getTarget())) { |
| 3220 | 16 => 0, |
| 3221 | 32 => 1, |
| 3222 | 64 => 2, |
| 3223 | 80 => 3, |
| 3224 | 128 => 4, |
| 3225 | else => unreachable, |
| 3226 | }; |
| 3227 | return @enumFromInt(@intFromEnum(f16_func) + offset); |
| 3228 | } |
| 3229 | |
| 3230 | fn softFloatNegBlockPayload( |
| 3231 | l: *Legalize, |
| 3232 | orig_inst: Air.Inst.Index, |
| 3233 | operand: Air.Inst.Ref, |
| 3234 | ) Error!Air.Inst.Data { |
| 3235 | const pt = l.pt; |
| 3236 | const zcu = pt.zcu; |
| 3237 | const gpa = zcu.gpa; |
| 3238 | |
| 3239 | const float_ty = l.typeOfIndex(orig_inst); |
| 3240 | |
| 3241 | const int_ty: Type, const sign_bit: Value = switch (float_ty.floatBits(zcu.getTarget())) { |
| 3242 | 16 => .{ .u16, try pt.intValue(.u16, @as(u16, 1) << 15) }, |
| 3243 | 32 => .{ .u32, try pt.intValue(.u32, @as(u32, 1) << 31) }, |
| 3244 | 64 => .{ .u64, try pt.intValue(.u64, @as(u64, 1) << 63) }, |
| 3245 | 80 => .{ .u80, try pt.intValue(.u80, @as(u80, 1) << 79) }, |
| 3246 | 128 => .{ .u128, try pt.intValue(.u128, @as(u128, 1) << 127) }, |
| 3247 | else => unreachable, |
| 3248 | }; |
| 3249 | |
| 3250 | const sign_bit_ref: Air.Inst.Ref = .fromValue(sign_bit); |
| 3251 | |
| 3252 | var inst_buf: [4]Air.Inst.Index = undefined; |
| 3253 | var main_block: Block = .init(&inst_buf); |
| 3254 | try l.air_instructions.ensureUnusedCapacity(gpa, inst_buf.len); |
| 3255 | |
| 3256 | const operand_as_int = main_block.addBitCast(l, int_ty, operand); |
| 3257 | const result_as_int = main_block.addBinOp(l, .xor, operand_as_int, sign_bit_ref).toRef(); |
| 3258 | const result = main_block.addBitCast(l, float_ty, result_as_int); |
| 3259 | main_block.addBr(l, orig_inst, result); |
| 3260 | |
| 3261 | return .{ .ty_pl = .{ |
| 3262 | .ty = .fromType(float_ty), |
| 3263 | .payload = try l.addBlockBody(main_block.body()), |
| 3264 | } }; |
| 3265 | } |
| 3266 | |
| 3267 | fn softFloatDivTruncFloorBlockPayload( |
| 3268 | l: *Legalize, |
| 3269 | orig_inst: Air.Inst.Index, |
| 3270 | lhs: Air.Inst.Ref, |
| 3271 | rhs: Air.Inst.Ref, |
| 3272 | air_tag: Air.Inst.Tag, |
| 3273 | ) Error!Air.Inst.Data { |
| 3274 | const zcu = l.pt.zcu; |
| 3275 | const gpa = zcu.gpa; |
| 3276 | |
| 3277 | const float_ty = l.typeOfIndex(orig_inst); |
| 3278 | |
| 3279 | const floor_tag: Air.Inst.Tag = switch (air_tag) { |
| 3280 | .div_trunc, .div_trunc_optimized => .trunc_float, |
| 3281 | .div_floor, .div_floor_optimized => .floor, |
| 3282 | else => unreachable, |
| 3283 | }; |
| 3284 | |
| 3285 | var inst_buf: [4]Air.Inst.Index = undefined; |
| 3286 | var main_block: Block = .init(&inst_buf); |
| 3287 | try l.air_instructions.ensureUnusedCapacity(gpa, inst_buf.len); |
| 3288 | |
| 3289 | const div_inst = try main_block.addCompilerRtCall(l, softFloatFunc(.div_float, float_ty, zcu), &.{ lhs, rhs }); |
| 3290 | const floor_inst = try main_block.addCompilerRtCall(l, softFloatFunc(floor_tag, float_ty, zcu), &.{div_inst.toRef()}); |
| 3291 | const casted_result = main_block.addBitCast(l, float_ty, floor_inst.toRef()); |
| 3292 | main_block.addBr(l, orig_inst, casted_result); |
| 3293 | |
| 3294 | return .{ .ty_pl = .{ |
| 3295 | .ty = .fromType(float_ty), |
| 3296 | .payload = try l.addBlockBody(main_block.body()), |
| 3297 | } }; |
| 3298 | } |
| 3299 | fn softFloatModBlockPayload( |
| 3300 | l: *Legalize, |
| 3301 | orig_inst: Air.Inst.Index, |
| 3302 | lhs: Air.Inst.Ref, |
| 3303 | rhs: Air.Inst.Ref, |
| 3304 | ) Error!Air.Inst.Data { |
| 3305 | const pt = l.pt; |
| 3306 | const zcu = pt.zcu; |
| 3307 | const gpa = zcu.gpa; |
| 3308 | |
| 3309 | const float_ty = l.typeOfIndex(orig_inst); |
| 3310 | |
| 3311 | var inst_buf: [10]Air.Inst.Index = undefined; |
| 3312 | var main_block: Block = .init(&inst_buf); |
| 3313 | try l.air_instructions.ensureUnusedCapacity(gpa, inst_buf.len); |
| 3314 | |
| 3315 | const rem = try main_block.addCompilerRtCall(l, softFloatFunc(.rem, float_ty, zcu), &.{ lhs, rhs }); |
| 3316 | const lhs_lt_zero = try main_block.addSoftFloatCmp(l, float_ty, .lt, lhs, .fromValue(try pt.floatValue(float_ty, 0.0))); |
| 3317 | |
| 3318 | var condbr: CondBr = .init(l, lhs_lt_zero, &main_block, .{}); |
| 3319 | condbr.then_block = .init(main_block.stealRemainingCapacity()); |
| 3320 | { |
| 3321 | const add = try condbr.then_block.addCompilerRtCall(l, softFloatFunc(.add, float_ty, zcu), &.{ rem.toRef(), rhs }); |
| 3322 | const inner_rem = try condbr.then_block.addCompilerRtCall(l, softFloatFunc(.rem, float_ty, zcu), &.{ add.toRef(), rhs }); |
| 3323 | const casted_result = condbr.then_block.addBitCast(l, float_ty, inner_rem.toRef()); |
| 3324 | condbr.then_block.addBr(l, orig_inst, casted_result); |
| 3325 | } |
| 3326 | condbr.else_block = .init(condbr.then_block.stealRemainingCapacity()); |
| 3327 | { |
| 3328 | const casted_result = condbr.else_block.addBitCast(l, float_ty, rem.toRef()); |
| 3329 | condbr.else_block.addBr(l, orig_inst, casted_result); |
| 3330 | } |
| 3331 | |
| 3332 | try condbr.finish(l); |
| 3333 | |
| 3334 | return .{ .ty_pl = .{ |
| 3335 | .ty = .fromType(float_ty), |
| 3336 | .payload = try l.addBlockBody(main_block.body()), |
| 3337 | } }; |
| 3338 | } |
| 3339 | fn softFloatCmpBlockPayload( |
| 3340 | l: *Legalize, |
| 3341 | orig_inst: Air.Inst.Index, |
| 3342 | float_ty: Type, |
| 3343 | op: std.math.CompareOperator, |
| 3344 | lhs: Air.Inst.Ref, |
| 3345 | rhs: Air.Inst.Ref, |
| 3346 | ) Error!Air.Inst.Data { |
| 3347 | const pt = l.pt; |
| 3348 | const gpa = pt.zcu.gpa; |
| 3349 | |
| 3350 | var inst_buf: [3]Air.Inst.Index = undefined; |
| 3351 | var main_block: Block = .init(&inst_buf); |
| 3352 | try l.air_instructions.ensureUnusedCapacity(gpa, inst_buf.len); |
| 3353 | |
| 3354 | const result = try main_block.addSoftFloatCmp(l, float_ty, op, lhs, rhs); |
| 3355 | main_block.addBr(l, orig_inst, result); |
| 3356 | |
| 3357 | return .{ .ty_pl = .{ |
| 3358 | .ty = .bool_type, |
| 3359 | .payload = try l.addBlockBody(main_block.body()), |
| 3360 | } }; |
| 3361 | } |
| 3362 | |
| 3363 | /// `inline` to propagate potentially comptime-known return value. |
| 3364 | inline fn wantScalarizeOrSoftFloat( |
| 3365 | l: *const Legalize, |
| 3366 | comptime air_tag: Air.Inst.Tag, |
| 3367 | ty: Type, |
| 3368 | ) enum { |
| 3369 | none, |
| 3370 | scalarize, |
| 3371 | soft_float, |
| 3372 | } { |
| 3373 | const zcu = l.pt.zcu; |
| 3374 | const is_vec, const scalar_ty = switch (ty.zigTypeTag(zcu)) { |
| 3375 | .vector => .{ true, ty.childType(zcu) }, |
| 3376 | else => .{ false, ty }, |
| 3377 | }; |
| 3378 | |
| 3379 | if (is_vec and l.features.has(.scalarize(air_tag))) return .scalarize; |
| 3380 | |
| 3381 | if (l.wantSoftFloatScalar(scalar_ty)) { |
| 3382 | return if (is_vec) .scalarize else .soft_float; |
| 3383 | } |
| 3384 | return .none; |
| 3385 | } |
| 3386 | |
| 3387 | /// `inline` to propagate potentially comptime-known return value. |
| 3388 | inline fn wantSoftFloatScalar(l: *const Legalize, ty: Type) bool { |
| 3389 | const zcu = l.pt.zcu; |
| 3390 | return switch (ty.zigTypeTag(zcu)) { |
| 3391 | .vector => unreachable, |
| 3392 | .float => switch (ty.floatBits(zcu.getTarget())) { |
| 3393 | 16 => l.features.has(.soft_f16), |
| 3394 | 32 => l.features.has(.soft_f32), |
| 3395 | 64 => l.features.has(.soft_f64), |
| 3396 | 80 => l.features.has(.soft_f80), |
| 3397 | 128 => l.features.has(.soft_f128), |
| 3398 | else => unreachable, |
| 3399 | }, |
| 3400 | else => false, |
| 3401 | }; |
| 3402 | } |
| 3403 | |
| 2528 | 3404 | const Air = @import("../Air.zig"); |
| 2529 | 3405 | const assert = std.debug.assert; |
| 2530 | 3406 | const dev = @import("../dev.zig"); |