| ... | @@ -632,11 +632,15 @@ const DeclGen = struct { | ... | @@ -632,11 +632,15 @@ const DeclGen = struct { |
| 632 | /// Checks whether the type can be directly translated to SPIR-V vectors | 632 | /// Checks whether the type can be directly translated to SPIR-V vectors |
| 633 | fn isVector(self: *DeclGen, ty: Type) bool { | 633 | fn isVector(self: *DeclGen, ty: Type) bool { |
| 634 | const mod = self.module; | 634 | const mod = self.module; |
| | 635 | const target = self.getTarget(); |
| 635 | if (ty.zigTypeTag(mod) != .Vector) return false; | 636 | if (ty.zigTypeTag(mod) != .Vector) return false; |
| 636 | const elem_ty = ty.childType(mod); | 637 | const elem_ty = ty.childType(mod); |
| | 638 | |
| 637 | const len = ty.vectorLen(mod); | 639 | const len = ty.vectorLen(mod); |
| 638 | const is_scalar = elem_ty.isNumeric(mod) or elem_ty.toIntern() == .bool_type; | 640 | const is_scalar = elem_ty.isNumeric(mod) or elem_ty.toIntern() == .bool_type; |
| 639 | return is_scalar and len > 1 and len <= 4; | 641 | const spirv_len = len > 1 and len <= 4; |
| | 642 | const opencl_len = if (target.os.tag == .opencl) (len == 8 or len == 16) else false; |
| | 643 | return is_scalar and (spirv_len or opencl_len); |
| 640 | } | 644 | } |
| 641 | | 645 | |
| 642 | fn arithmeticTypeInfo(self: *DeclGen, ty: Type) ArithmeticTypeInfo { | 646 | fn arithmeticTypeInfo(self: *DeclGen, ty: Type) ArithmeticTypeInfo { |
| ... | @@ -1968,7 +1972,10 @@ const DeclGen = struct { | ... | @@ -1968,7 +1972,10 @@ const DeclGen = struct { |
| 1968 | try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{ | 1972 | try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{ |
| 1969 | .id_result_type = self.typeId(return_ty_ref), | 1973 | .id_result_type = self.typeId(return_ty_ref), |
| 1970 | .id_result = decl_id, | 1974 | .id_result = decl_id, |
| 1971 | .function_control = .{}, // TODO: We can set inline here if the type requires it. | 1975 | .function_control = switch (fn_info.cc) { |
| | 1976 | .Inline => .{ .Inline = true }, |
| | 1977 | else => .{}, |
| | 1978 | }, |
| 1972 | .function_type = prototype_id, | 1979 | .function_type = prototype_id, |
| 1973 | }); | 1980 | }); |
| 1974 | | 1981 | |
| ... | @@ -2437,48 +2444,71 @@ const DeclGen = struct { | ... | @@ -2437,48 +2444,71 @@ const DeclGen = struct { |
| 2437 | | 2444 | |
| 2438 | fn minMax(self: *DeclGen, result_ty: Type, op: std.math.CompareOperator, lhs_id: IdRef, rhs_id: IdRef) !IdRef { | 2445 | fn minMax(self: *DeclGen, result_ty: Type, op: std.math.CompareOperator, lhs_id: IdRef, rhs_id: IdRef) !IdRef { |
| 2439 | const info = self.arithmeticTypeInfo(result_ty); | 2446 | const info = self.arithmeticTypeInfo(result_ty); |
| | 2447 | const target = self.getTarget(); |
| 2440 | | 2448 | |
| 2441 | var wip = try self.elementWise(result_ty, true); | 2449 | const use_backup_codegen = target.os.tag == .opencl and info.class != .float; |
| | 2450 | var wip = try self.elementWise(result_ty, use_backup_codegen); |
| 2442 | defer wip.deinit(); | 2451 | defer wip.deinit(); |
| | 2452 | |
| 2443 | for (wip.results, 0..) |*result_id, i| { | 2453 | for (wip.results, 0..) |*result_id, i| { |
| 2444 | const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i); | 2454 | const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i); |
| 2445 | const rhs_elem_id = try wip.elementAt(result_ty, rhs_id, i); | 2455 | const rhs_elem_id = try wip.elementAt(result_ty, rhs_id, i); |
| 2446 | | 2456 | |
| 2447 | // TODO: Use fmin for OpenCL | 2457 | if (use_backup_codegen) { |
| 2448 | const cmp_id = try self.cmp(op, Type.bool, wip.ty, lhs_elem_id, rhs_elem_id); | 2458 | const cmp_id = try self.cmp(op, Type.bool, wip.ty, lhs_elem_id, rhs_elem_id); |
| 2449 | const selection_id = switch (info.class) { | 2459 | result_id.* = self.spv.allocId(); |
| 2450 | .float => blk: { | 2460 | try self.func.body.emit(self.spv.gpa, .OpSelect, .{ |
| 2451 | // cmp uses OpFOrd. When we have 0 [<>] nan this returns false, | 2461 | .id_result_type = wip.ty_id, |
| 2452 | // but we want it to pick lhs. Therefore we also have to check if | 2462 | .id_result = result_id.*, |
| 2453 | // rhs is nan. We don't need to care about the result when both | 2463 | .condition = cmp_id, |
| 2454 | // are nan. | 2464 | .object_1 = lhs_elem_id, |
| 2455 | const rhs_is_nan_id = self.spv.allocId(); | 2465 | .object_2 = rhs_elem_id, |
| 2456 | const bool_ty_ref = try self.resolveType(Type.bool, .direct); | 2466 | }); |
| 2457 | try self.func.body.emit(self.spv.gpa, .OpIsNan, .{ | 2467 | } else { |
| 2458 | .id_result_type = self.typeId(bool_ty_ref), | 2468 | const ext_inst: Word = switch (target.os.tag) { |
| 2459 | .id_result = rhs_is_nan_id, | 2469 | .opencl => switch (op) { |
| 2460 | .x = rhs_elem_id, | 2470 | .lt => 28, // fmin |
| 2461 | }); | 2471 | .gt => 27, // fmax |
| 2462 | const float_cmp_id = self.spv.allocId(); | 2472 | else => unreachable, |
| 2463 | try self.func.body.emit(self.spv.gpa, .OpLogicalOr, .{ | 2473 | }, |
| 2464 | .id_result_type = self.typeId(bool_ty_ref), | 2474 | .vulkan => switch (info.class) { |
| 2465 | .id_result = float_cmp_id, | 2475 | .float => switch (op) { |
| 2466 | .operand_1 = cmp_id, | 2476 | .lt => 37, // FMin |
| 2467 | .operand_2 = rhs_is_nan_id, | 2477 | .gt => 40, // FMax |
| 2468 | }); | 2478 | else => unreachable, |
| 2469 | break :blk float_cmp_id; | 2479 | }, |
| 2470 | }, | 2480 | .integer, .strange_integer => switch (info.signedness) { |
| 2471 | else => cmp_id, | 2481 | .signed => switch (op) { |
| 2472 | }; | 2482 | .lt => 39, // SMin |
| | 2483 | .gt => 42, // SMax |
| | 2484 | else => unreachable, |
| | 2485 | }, |
| | 2486 | .unsigned => switch (op) { |
| | 2487 | .lt => 38, // UMin |
| | 2488 | .gt => 41, // UMax |
| | 2489 | else => unreachable, |
| | 2490 | }, |
| | 2491 | }, |
| | 2492 | .composite_integer => unreachable, // TODO |
| | 2493 | .bool => unreachable, |
| | 2494 | }, |
| | 2495 | else => unreachable, |
| | 2496 | }; |
| | 2497 | const set_id = switch (target.os.tag) { |
| | 2498 | .opencl => try self.spv.importInstructionSet(.opencl), |
| | 2499 | .vulkan => try self.spv.importInstructionSet(.glsl), |
| | 2500 | else => unreachable, |
| | 2501 | }; |
| 2473 | | 2502 | |
| 2474 | result_id.* = self.spv.allocId(); | 2503 | result_id.* = self.spv.allocId(); |
| 2475 | try self.func.body.emit(self.spv.gpa, .OpSelect, .{ | 2504 | try self.func.body.emit(self.spv.gpa, .OpExtInst, .{ |
| 2476 | .id_result_type = wip.ty_id, | 2505 | .id_result_type = wip.ty_id, |
| 2477 | .id_result = result_id.*, | 2506 | .id_result = result_id.*, |
| 2478 | .condition = selection_id, | 2507 | .set = set_id, |
| 2479 | .object_1 = lhs_elem_id, | 2508 | .instruction = .{ .inst = ext_inst }, |
| 2480 | .object_2 = rhs_elem_id, | 2509 | .id_ref_4 = &.{ lhs_elem_id, rhs_elem_id }, |
| 2481 | }); | 2510 | }); |
| | 2511 | } |
| 2482 | } | 2512 | } |
| 2483 | return wip.finalize(); | 2513 | return wip.finalize(); |
| 2484 | } | 2514 | } |
| ... | @@ -2607,57 +2637,52 @@ const DeclGen = struct { | ... | @@ -2607,57 +2637,52 @@ const DeclGen = struct { |
| 2607 | } | 2637 | } |
| 2608 | | 2638 | |
| 2609 | fn airAbs(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { | 2639 | fn airAbs(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 2610 | const mod = self.module; | 2640 | const target = self.getTarget(); |
| 2611 | const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; | 2641 | const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 2612 | const operand_id = try self.resolve(ty_op.operand); | 2642 | const operand_id = try self.resolve(ty_op.operand); |
| 2613 | // Note: operand_ty may be signed, while ty is always unsigned! | 2643 | // Note: operand_ty may be signed, while ty is always unsigned! |
| 2614 | const operand_ty = self.typeOf(ty_op.operand); | 2644 | const operand_ty = self.typeOf(ty_op.operand); |
| 2615 | const result_ty = self.typeOfIndex(inst); | 2645 | const result_ty = self.typeOfIndex(inst); |
| 2616 | const info = self.arithmeticTypeInfo(result_ty); | 2646 | const operand_info = self.arithmeticTypeInfo(operand_ty); |
| 2617 | const operand_scalar_ty = operand_ty.scalarType(mod); | | |
| 2618 | const operand_scalar_ty_ref = try self.resolveType(operand_scalar_ty, .direct); | | |
| 2619 | | 2647 | |
| 2620 | var wip = try self.elementWise(result_ty, true); | 2648 | var wip = try self.elementWise(result_ty, false); |
| 2621 | defer wip.deinit(); | 2649 | defer wip.deinit(); |
| 2622 | | 2650 | |
| 2623 | const zero_id = switch (info.class) { | | |
| 2624 | .float => try self.constFloat(operand_scalar_ty_ref, 0), | | |
| 2625 | .integer, .strange_integer => try self.constInt(operand_scalar_ty_ref, 0), | | |
| 2626 | .composite_integer => unreachable, // TODO | | |
| 2627 | .bool => unreachable, | | |
| 2628 | }; | | |
| 2629 | for (wip.results, 0..) |*result_id, i| { | 2651 | for (wip.results, 0..) |*result_id, i| { |
| 2630 | const elem_id = try wip.elementAt(operand_ty, operand_id, i); | 2652 | const elem_id = try wip.elementAt(operand_ty, operand_id, i); |
| 2631 | // Idk why spir-v doesn't have a dedicated abs() instruction in the base | 2653 | |
| 2632 | // instruction set. For now we're just going to negate and check to avoid | 2654 | const ext_inst: Word = switch (target.os.tag) { |
| 2633 | // importing the extinst. | 2655 | .opencl => switch (operand_info.class) { |
| 2634 | // TODO: Make this a call to compiler rt / ext inst | 2656 | .float => 23, // fabs |
| 2635 | const neg_id = self.spv.allocId(); | 2657 | .integer, .strange_integer => switch (operand_info.signedness) { |
| 2636 | const args = .{ | 2658 | .signed => 141, // s_abs |
| 2637 | .id_result_type = self.typeId(operand_scalar_ty_ref), | 2659 | .unsigned => 201, // u_abs |
| 2638 | .id_result = neg_id, | 2660 | }, |
| 2639 | .operand_1 = zero_id, | 2661 | .composite_integer => unreachable, // TODO |
| 2640 | .operand_2 = elem_id, | 2662 | .bool => unreachable, |
| | 2663 | }, |
| | 2664 | .vulkan => switch (operand_info.class) { |
| | 2665 | .float => 4, // FAbs |
| | 2666 | .integer, .strange_integer => 5, // SAbs |
| | 2667 | .composite_integer => unreachable, // TODO |
| | 2668 | .bool => unreachable, |
| | 2669 | }, |
| | 2670 | else => unreachable, |
| 2641 | }; | 2671 | }; |
| 2642 | switch (info.class) { | 2672 | const set_id = switch (target.os.tag) { |
| 2643 | .float => try self.func.body.emit(self.spv.gpa, .OpFSub, args), | 2673 | .opencl => try self.spv.importInstructionSet(.opencl), |
| 2644 | .integer, .strange_integer => try self.func.body.emit(self.spv.gpa, .OpISub, args), | 2674 | .vulkan => try self.spv.importInstructionSet(.glsl), |
| 2645 | .composite_integer => unreachable, // TODO | 2675 | else => unreachable, |
| 2646 | .bool => unreachable, | 2676 | }; |
| 2647 | } | 2677 | |
| 2648 | const neg_norm_id = try self.normalize(wip.ty_ref, neg_id, info); | 2678 | result_id.* = self.spv.allocId(); |
| 2649 | | 2679 | try self.func.body.emit(self.spv.gpa, .OpExtInst, .{ |
| 2650 | const gt_zero_id = try self.cmp(.gt, Type.bool, operand_scalar_ty, elem_id, zero_id); | 2680 | .id_result_type = wip.ty_id, |
| 2651 | const abs_id = self.spv.allocId(); | 2681 | .id_result = result_id.*, |
| 2652 | try self.func.body.emit(self.spv.gpa, .OpSelect, .{ | 2682 | .set = set_id, |
| 2653 | .id_result_type = self.typeId(operand_scalar_ty_ref), | 2683 | .instruction = .{ .inst = ext_inst }, |
| 2654 | .id_result = abs_id, | 2684 | .id_ref_4 = &.{elem_id}, |
| 2655 | .condition = gt_zero_id, | | |
| 2656 | .object_1 = elem_id, | | |
| 2657 | .object_2 = neg_norm_id, | | |
| 2658 | }); | 2685 | }); |
| 2659 | // For Shader, we may need to cast from signed to unsigned here. | | |
| 2660 | result_id.* = try self.bitCast(wip.ty, operand_scalar_ty, abs_id); | | |
| 2661 | } | 2686 | } |
| 2662 | return try wip.finalize(); | 2687 | return try wip.finalize(); |
| 2663 | } | 2688 | } |