| ... | ... | @@ -442,6 +442,43 @@ pub const DeclGen = struct { |
| 442 | 442 | } |
| 443 | 443 | } |
| 444 | 444 | |
| 445 | /// Construct a struct at runtime. |
| 446 | /// result_ty_ref must be a struct type. |
| 447 | fn constructStruct(self: *DeclGen, result_ty_ref: SpvType.Ref, constituents: []const IdRef) !IdRef { |
| 448 | // The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which' |
| 449 | // operands are not constant. |
| 450 | // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349 |
| 451 | // For now, just initialize the struct by setting the fields manually... |
| 452 | // TODO: Make this OpCompositeConstruct when we can |
| 453 | const ptr_composite_id = try self.alloc(result_ty_ref, null); |
| 454 | // Note: using 32-bit ints here because usize crashes the translator as well |
| 455 | const index_ty_ref = try self.intType(.unsigned, 32); |
| 456 | const spv_composite_ty = self.spv.typeRefType(result_ty_ref); |
| 457 | const members = spv_composite_ty.payload(.@"struct").members; |
| 458 | for (constituents, members, 0..) |constitent_id, member, index| { |
| 459 | const index_id = try self.constInt(index_ty_ref, index); |
| 460 | const ptr_id = self.spv.allocId(); |
| 461 | const ptr_member_ty_ref = try self.spv.ptrType(member.ty, .Generic, 0); |
| 462 | try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ |
| 463 | .id_result_type = self.typeId(ptr_member_ty_ref), |
| 464 | .id_result = ptr_id, |
| 465 | .base = ptr_composite_id, |
| 466 | .indexes = &.{index_id}, |
| 467 | }); |
| 468 | try self.func.body.emit(self.spv.gpa, .OpStore, .{ |
| 469 | .pointer = ptr_id, |
| 470 | .object = constitent_id, |
| 471 | }); |
| 472 | } |
| 473 | const result_id = self.spv.allocId(); |
| 474 | try self.func.body.emit(self.spv.gpa, .OpLoad, .{ |
| 475 | .id_result_type = self.typeId(result_ty_ref), |
| 476 | .id_result = result_id, |
| 477 | .pointer = ptr_composite_id, |
| 478 | }); |
| 479 | return result_id; |
| 480 | } |
| 481 | |
| 445 | 482 | const IndirectConstantLowering = struct { |
| 446 | 483 | const undef = 0xAA; |
| 447 | 484 | |
| ... | ... | @@ -1582,6 +1619,20 @@ pub const DeclGen = struct { |
| 1582 | 1619 | } |
| 1583 | 1620 | } |
| 1584 | 1621 | |
| 1622 | fn boolToInt(self: *DeclGen, result_ty_ref: SpvType.Ref, condition_id: IdRef) !IdRef { |
| 1623 | const zero_id = try self.constInt(result_ty_ref, 0); |
| 1624 | const one_id = try self.constInt(result_ty_ref, 1); |
| 1625 | const result_id = self.spv.allocId(); |
| 1626 | try self.func.body.emit(self.spv.gpa, .OpSelect, .{ |
| 1627 | .id_result_type = self.typeId(result_ty_ref), |
| 1628 | .id_result = result_id, |
| 1629 | .condition = condition_id, |
| 1630 | .object_1 = one_id, |
| 1631 | .object_2 = zero_id, |
| 1632 | }); |
| 1633 | return result_id; |
| 1634 | } |
| 1635 | |
| 1585 | 1636 | /// Convert representation from indirect (in memory) to direct (in 'register') |
| 1586 | 1637 | /// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct). |
| 1587 | 1638 | fn convertToDirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef { |
| ... | ... | @@ -1610,17 +1661,7 @@ pub const DeclGen = struct { |
| 1610 | 1661 | return switch (ty.zigTypeTag()) { |
| 1611 | 1662 | .Bool => blk: { |
| 1612 | 1663 | const indirect_bool_ty_ref = try self.resolveType(ty, .indirect); |
| 1613 | | const zero_id = try self.constInt(indirect_bool_ty_ref, 0); |
| 1614 | | const one_id = try self.constInt(indirect_bool_ty_ref, 1); |
| 1615 | | const result_id = self.spv.allocId(); |
| 1616 | | try self.func.body.emit(self.spv.gpa, .OpSelect, .{ |
| 1617 | | .id_result_type = self.typeId(indirect_bool_ty_ref), |
| 1618 | | .id_result = result_id, |
| 1619 | | .condition = operand_id, |
| 1620 | | .object_1 = one_id, |
| 1621 | | .object_2 = zero_id, |
| 1622 | | }); |
| 1623 | | break :blk result_id; |
| 1664 | break :blk self.boolToInt(indirect_bool_ty_ref, operand_id); |
| 1624 | 1665 | }, |
| 1625 | 1666 | else => operand_id, |
| 1626 | 1667 | }; |
| ... | ... | @@ -1933,64 +1974,83 @@ pub const DeclGen = struct { |
| 1933 | 1974 | .float, .bool => unreachable, |
| 1934 | 1975 | } |
| 1935 | 1976 | |
| 1936 | | // The operand type must be the same as the result type in SPIR-V. |
| 1977 | // The operand type must be the same as the result type in SPIR-V, which |
| 1978 | // is the same as in Zig. |
| 1937 | 1979 | const operand_ty_ref = try self.resolveType(operand_ty, .direct); |
| 1938 | 1980 | const operand_ty_id = self.typeId(operand_ty_ref); |
| 1939 | 1981 | |
| 1940 | | const op_result_id = blk: { |
| 1941 | | // Construct the SPIR-V result type. |
| 1942 | | // It is almost the same as the zig one, except that the fields must be the same type |
| 1943 | | // and they must be unsigned. |
| 1944 | | const overflow_result_ty_ref = try self.spv.simpleStructType(&.{ |
| 1945 | | .{ .ty = operand_ty_ref, .name = "res" }, |
| 1946 | | .{ .ty = operand_ty_ref, .name = "ov" }, |
| 1947 | | }); |
| 1948 | | const result_id = self.spv.allocId(); |
| 1949 | | try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{ |
| 1950 | | .id_result_type = self.typeId(overflow_result_ty_ref), |
| 1951 | | .id_result = result_id, |
| 1952 | | .operand_1 = lhs, |
| 1953 | | .operand_2 = rhs, |
| 1954 | | }); |
| 1955 | | break :blk result_id; |
| 1956 | | }; |
| 1957 | | |
| 1958 | | // Now convert the SPIR-V flavor result into a Zig-flavor result. |
| 1959 | | // First, extract the two fields. |
| 1960 | | const unsigned_result = try self.extractField(operand_ty, op_result_id, 0); |
| 1961 | | const overflow = try self.extractField(operand_ty, op_result_id, 1); |
| 1962 | | |
| 1963 | | // We need to convert the results to the types that Zig expects here. |
| 1964 | | // The `result` is the same type except unsigned, so we can just bitcast that. |
| 1965 | | // TODO: This can be removed in Kernels as there are only unsigned ints. Maybe for |
| 1966 | | // shaders as well? |
| 1967 | | const result = try self.bitcast(operand_ty_id, unsigned_result); |
| 1968 | | |
| 1969 | | // The overflow needs to be converted into whatever is used to represent it in Zig. |
| 1970 | | const casted_overflow = blk: { |
| 1971 | | const ov_ty = result_ty.tupleFields().types[1]; |
| 1972 | | const ov_ty_id = try self.resolveTypeId(ov_ty); |
| 1973 | | const result_id = self.spv.allocId(); |
| 1974 | | try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ |
| 1975 | | .id_result_type = ov_ty_id, |
| 1976 | | .id_result = result_id, |
| 1977 | | .unsigned_value = overflow, |
| 1978 | | }); |
| 1979 | | break :blk result_id; |
| 1980 | | }; |
| 1982 | const bool_ty_ref = try self.resolveType(Type.bool, .direct); |
| 1981 | 1983 | |
| 1982 | | // TODO: If copying this function for borrow, make sure to convert -1 to 1 as appropriate. |
| 1984 | const ov_ty = result_ty.tupleFields().types[1]; |
| 1985 | // Note: result is stored in a struct, so indirect representation. |
| 1986 | const ov_ty_ref = try self.resolveType(ov_ty, .indirect); |
| 1983 | 1987 | |
| 1984 | | // Finally, construct the Zig type. |
| 1985 | | // Layout is result, overflow. |
| 1986 | | const result_id = self.spv.allocId(); |
| 1987 | | const constituents = [_]IdRef{ result, casted_overflow }; |
| 1988 | | try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{ |
| 1988 | // TODO: Operations other than addition. |
| 1989 | const value_id = self.spv.allocId(); |
| 1990 | try self.func.body.emit(self.spv.gpa, .OpIAdd, .{ |
| 1989 | 1991 | .id_result_type = operand_ty_id, |
| 1990 | | .id_result = result_id, |
| 1991 | | .constituents = &constituents, |
| 1992 | .id_result = value_id, |
| 1993 | .operand_1 = lhs, |
| 1994 | .operand_2 = rhs, |
| 1995 | }); |
| 1996 | |
| 1997 | const overflowed_id = switch (info.signedness) { |
| 1998 | .unsigned => blk: { |
| 1999 | // Overflow happened if the result is smaller than either of the operands. It doesn't matter which. |
| 2000 | const overflowed_id = self.spv.allocId(); |
| 2001 | try self.func.body.emit(self.spv.gpa, .OpULessThan, .{ |
| 2002 | .id_result_type = self.typeId(bool_ty_ref), |
| 2003 | .id_result = overflowed_id, |
| 2004 | .operand_1 = value_id, |
| 2005 | .operand_2 = lhs, |
| 2006 | }); |
| 2007 | break :blk overflowed_id; |
| 2008 | }, |
| 2009 | .signed => blk: { |
| 2010 | // Overflow happened if: |
| 2011 | // - rhs is negative and value > lhs |
| 2012 | // - rhs is positive and value < lhs |
| 2013 | // This can be shortened to: |
| 2014 | // (rhs < 0 && value > lhs) || (rhs >= 0 && value <= lhs) |
| 2015 | // = (rhs < 0) == (value > lhs) |
| 2016 | // Note that signed overflow is also wrapping in spir-v. |
| 2017 | |
| 2018 | const rhs_lt_zero_id = self.spv.allocId(); |
| 2019 | const zero_id = try self.constInt(operand_ty_ref, 0); |
| 2020 | try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{ |
| 2021 | .id_result_type = self.typeId(bool_ty_ref), |
| 2022 | .id_result = rhs_lt_zero_id, |
| 2023 | .operand_1 = rhs, |
| 2024 | .operand_2 = zero_id, |
| 2025 | }); |
| 2026 | |
| 2027 | const value_gt_lhs_id = self.spv.allocId(); |
| 2028 | try self.func.body.emit(self.spv.gpa, .OpSGreaterThan, .{ |
| 2029 | .id_result_type = self.typeId(bool_ty_ref), |
| 2030 | .id_result = value_gt_lhs_id, |
| 2031 | .operand_1 = value_id, |
| 2032 | .operand_2 = lhs, |
| 2033 | }); |
| 2034 | |
| 2035 | const overflowed_id = self.spv.allocId(); |
| 2036 | try self.func.body.emit(self.spv.gpa, .OpLogicalEqual, .{ |
| 2037 | .id_result_type = self.typeId(bool_ty_ref), |
| 2038 | .id_result = overflowed_id, |
| 2039 | .operand_1 = rhs_lt_zero_id, |
| 2040 | .operand_2 = value_gt_lhs_id, |
| 2041 | }); |
| 2042 | break :blk overflowed_id; |
| 2043 | }, |
| 2044 | }; |
| 2045 | |
| 2046 | // Construct the struct that Zig wants as result. |
| 2047 | // The value should already be the correct type. |
| 2048 | const ov_id = try self.boolToInt(ov_ty_ref, overflowed_id); |
| 2049 | const result_ty_ref = try self.resolveType(result_ty, .direct); |
| 2050 | return try self.constructStruct(result_ty_ref, &.{ |
| 2051 | value_id, |
| 2052 | ov_id, |
| 1992 | 2053 | }); |
| 1993 | | return result_id; |
| 1994 | 2054 | } |
| 1995 | 2055 | |
| 1996 | 2056 | fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| ... | ... | @@ -2463,76 +2523,45 @@ pub const DeclGen = struct { |
| 2463 | 2523 | return result_id; |
| 2464 | 2524 | } |
| 2465 | 2525 | |
| 2466 | | fn variable( |
| 2526 | // Allocate a function-local variable, with possible initializer. |
| 2527 | // This function returns a pointer to a variable of type `ty_ref`, |
| 2528 | // which is in the Generic address space. The variable is actually |
| 2529 | // placed in the Function address space. |
| 2530 | fn alloc( |
| 2467 | 2531 | self: *DeclGen, |
| 2468 | | comptime context: enum { function, global }, |
| 2469 | | result_id: IdRef, |
| 2470 | | ptr_ty_ref: SpvType.Ref, |
| 2532 | ty_ref: SpvType.Ref, |
| 2471 | 2533 | initializer: ?IdRef, |
| 2472 | | ) !void { |
| 2473 | | const storage_class = self.spv.typeRefType(ptr_ty_ref).payload(.pointer).storage_class; |
| 2474 | | const actual_storage_class = switch (storage_class) { |
| 2475 | | .Generic => switch (context) { |
| 2476 | | .function => .Function, |
| 2477 | | .global => .CrossWorkgroup, |
| 2478 | | }, |
| 2479 | | else => storage_class, |
| 2480 | | }; |
| 2481 | | const actual_ptr_ty_ref = switch (storage_class) { |
| 2482 | | .Generic => try self.spv.changePtrStorageClass(ptr_ty_ref, actual_storage_class), |
| 2483 | | else => ptr_ty_ref, |
| 2484 | | }; |
| 2485 | | const alloc_result_id = switch (storage_class) { |
| 2486 | | .Generic => self.spv.allocId(), |
| 2487 | | else => result_id, |
| 2488 | | }; |
| 2534 | ) !IdRef { |
| 2535 | const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function, 0); |
| 2536 | const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0); |
| 2489 | 2537 | |
| 2490 | | const section = switch (actual_storage_class) { |
| 2491 | | .Generic => unreachable, |
| 2492 | | // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to |
| 2493 | | // directly generate them into func.prologue instead of the body. |
| 2494 | | .Function => &self.func.prologue, |
| 2495 | | else => &self.spv.sections.types_globals_constants, |
| 2496 | | }; |
| 2497 | | try section.emit(self.spv.gpa, .OpVariable, .{ |
| 2498 | | .id_result_type = self.typeId(actual_ptr_ty_ref), |
| 2499 | | .id_result = alloc_result_id, |
| 2500 | | .storage_class = actual_storage_class, |
| 2538 | // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to |
| 2539 | // directly generate them into func.prologue instead of the body. |
| 2540 | const var_id = self.spv.allocId(); |
| 2541 | try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{ |
| 2542 | .id_result_type = self.typeId(fn_ptr_ty_ref), |
| 2543 | .id_result = var_id, |
| 2544 | .storage_class = .Function, |
| 2501 | 2545 | .initializer = initializer, |
| 2502 | 2546 | }); |
| 2503 | 2547 | |
| 2504 | | if (storage_class != .Generic) { |
| 2505 | | return; |
| 2506 | | } |
| 2507 | | |
| 2508 | | // Now we need to convert the pointer. |
| 2509 | | // If this is a function local, we need to perform the conversion at runtime. Otherwise, we can do |
| 2510 | | // it ahead of time using OpSpecConstantOp. |
| 2511 | | switch (actual_storage_class) { |
| 2512 | | .Function => try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{ |
| 2513 | | .id_result_type = self.typeId(ptr_ty_ref), |
| 2514 | | .id_result = result_id, |
| 2515 | | .pointer = alloc_result_id, |
| 2516 | | }), |
| 2517 | | // TODO: Can we do without this cast or move it to runtime? |
| 2518 | | else => { |
| 2519 | | const const_ptr_id = try self.makePointerConstant(section, actual_ptr_ty_ref, alloc_result_id); |
| 2520 | | try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ |
| 2521 | | .id_result_type = self.typeId(ptr_ty_ref), |
| 2522 | | .id_result = result_id, |
| 2523 | | .pointer = const_ptr_id, |
| 2524 | | }); |
| 2525 | | }, |
| 2526 | | } |
| 2548 | // Convert to a generic pointer |
| 2549 | const result_id = self.spv.allocId(); |
| 2550 | try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{ |
| 2551 | .id_result_type = self.typeId(general_ptr_ty_ref), |
| 2552 | .id_result = result_id, |
| 2553 | .pointer = var_id, |
| 2554 | }); |
| 2555 | return result_id; |
| 2527 | 2556 | } |
| 2528 | 2557 | |
| 2529 | 2558 | fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 2530 | 2559 | if (self.liveness.isUnused(inst)) return null; |
| 2531 | | const ty = self.air.typeOfIndex(inst); |
| 2532 | | const result_ty_ref = try self.resolveType(ty, .direct); |
| 2533 | | const result_id = self.spv.allocId(); |
| 2534 | | try self.variable(.function, result_id, result_ty_ref, null); |
| 2535 | | return result_id; |
| 2560 | const ptr_ty = self.air.typeOfIndex(inst); |
| 2561 | assert(ptr_ty.ptrAddressSpace() == .generic); |
| 2562 | const child_ty = ptr_ty.childType(); |
| 2563 | const child_ty_ref = try self.resolveType(child_ty, .indirect); |
| 2564 | return try self.alloc(child_ty_ref, null); |
| 2536 | 2565 | } |
| 2537 | 2566 | |
| 2538 | 2567 | fn airArg(self: *DeclGen) IdRef { |
| ... | ... | @@ -2819,13 +2848,7 @@ pub const DeclGen = struct { |
| 2819 | 2848 | } |
| 2820 | 2849 | |
| 2821 | 2850 | const err_union_ty_ref = try self.resolveType(err_union_ty, .direct); |
| 2822 | | const result_id = self.spv.allocId(); |
| 2823 | | try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{ |
| 2824 | | .id_result_type = self.typeId(err_union_ty_ref), |
| 2825 | | .id_result = result_id, |
| 2826 | | .constituents = members.slice(), |
| 2827 | | }); |
| 2828 | | return result_id; |
| 2851 | return try self.constructStruct(err_union_ty_ref, members.slice()); |
| 2829 | 2852 | } |
| 2830 | 2853 | |
| 2831 | 2854 | fn airIsNull(self: *DeclGen, inst: Air.Inst.Index, pred: enum { is_null, is_non_null }) !?IdRef { |
| ... | ... | @@ -2925,14 +2948,8 @@ pub const DeclGen = struct { |
| 2925 | 2948 | } |
| 2926 | 2949 | |
| 2927 | 2950 | const optional_ty_ref = try self.resolveType(optional_ty, .direct); |
| 2928 | | const result_id = self.spv.allocId(); |
| 2929 | 2951 | const members = [_]IdRef{ operand_id, try self.constBool(true, .indirect) }; |
| 2930 | | try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{ |
| 2931 | | .id_result_type = self.typeId(optional_ty_ref), |
| 2932 | | .id_result = result_id, |
| 2933 | | .constituents = &members, |
| 2934 | | }); |
| 2935 | | return result_id; |
| 2952 | return try self.constructStruct(optional_ty_ref, &members); |
| 2936 | 2953 | } |
| 2937 | 2954 | |
| 2938 | 2955 | fn airSwitchBr(self: *DeclGen, inst: Air.Inst.Index) !void { |