authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2025-08-09 13:27:04+03:30
committergravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2025-08-09 13:27:04+03:30
logbed99e1ecd6c919f4c8a974e598302773db10b8a
treecec6a70f9b8ad74b0fa0eda5409e32093c460909
parent0d0f09fb0ee60b5fa42f51732bde2a4db43453a8
signaturelock-open Commit is signed but in an unrecognized format.

spirv: remove prune_unused ISel


5 files changed, 116 insertions(+), 484 deletions(-)

src/codegen/spirv/CodeGen.zig+96-105
...@@ -274,6 +274,13 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {...@@ -274,6 +274,13 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {
274 .storage_class = storage_class,274 .storage_class = storage_class,
275 });275 });
276276
277 if (nav.getAlignment() != ty.abiAlignment(zcu)) {
278 if (target.os.tag != .opencl) return cg.fail("cannot apply alignment to variables", .{});
279 try cg.module.decorate(result_id, .{
280 .alignment = .{ .alignment = @intCast(nav.getAlignment().toByteUnits().?) },
281 });
282 }
283
277 switch (target.os.tag) {284 switch (target.os.tag) {
278 .vulkan, .opengl => {285 .vulkan, .opengl => {
279 if (ty.zigTypeTag(zcu) == .@"struct") {286 if (ty.zigTypeTag(zcu) == .@"struct") {
...@@ -348,7 +355,7 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {...@@ -348,7 +355,7 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {
348 .id_result_type = ptr_ty_id,355 .id_result_type = ptr_ty_id,
349 .id_result = result_id,356 .id_result = result_id,
350 .set = try cg.module.importInstructionSet(.zig),357 .set = try cg.module.importInstructionSet(.zig),
351 .instruction = .{ .inst = 0 }, // TODO: Put this definition somewhere...358 .instruction = .{ .inst = @intFromEnum(spec.Zig.InvocationGlobal) },
352 .id_ref_4 = &.{initializer_id},359 .id_ref_4 = &.{initializer_id},
353 });360 });
354 } else {361 } else {
...@@ -356,7 +363,7 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {...@@ -356,7 +363,7 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {
356 .id_result_type = ptr_ty_id,363 .id_result_type = ptr_ty_id,
357 .id_result = result_id,364 .id_result = result_id,
358 .set = try cg.module.importInstructionSet(.zig),365 .set = try cg.module.importInstructionSet(.zig),
359 .instruction = .{ .inst = 0 }, // TODO: Put this definition somewhere...366 .instruction = .{ .inst = @intFromEnum(spec.Zig.InvocationGlobal) },
360 .id_ref_4 = &.{},367 .id_ref_4 = &.{},
361 });368 });
362 }369 }
...@@ -498,7 +505,7 @@ fn resolveUav(cg: *CodeGen, val: InternPool.Index) !Id {...@@ -498,7 +505,7 @@ fn resolveUav(cg: *CodeGen, val: InternPool.Index) !Id {
498 .id_result_type = fn_decl_ptr_ty_id,505 .id_result_type = fn_decl_ptr_ty_id,
499 .id_result = result_id,506 .id_result = result_id,
500 .set = try cg.module.importInstructionSet(.zig),507 .set = try cg.module.importInstructionSet(.zig),
501 .instruction = .{ .inst = 0 }, // TODO: Put this definition somewhere...508 .instruction = .{ .inst = @intFromEnum(spec.Zig.InvocationGlobal) },
502 .id_ref_4 = &.{initializer_id},509 .id_ref_4 = &.{initializer_id},
503 });510 });
504 }511 }
...@@ -1037,9 +1044,18 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {...@@ -1037,9 +1044,18 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
1037 const gpa = cg.module.gpa;1044 const gpa = cg.module.gpa;
1038 const pt = cg.pt;1045 const pt = cg.pt;
1039 const zcu = cg.module.zcu;1046 const zcu = cg.module.zcu;
1047 const target = zcu.getTarget();
1040 switch (derivation) {1048 switch (derivation) {
1041 .comptime_alloc_ptr, .comptime_field_ptr => unreachable,1049 .comptime_alloc_ptr, .comptime_field_ptr => unreachable,
1042 .int => |int| {1050 .int => |int| {
1051 if (target.os.tag != .opencl) {
1052 if (int.ptr_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) {
1053 return cg.fail(
1054 "cannot cast integer to pointer with address space '{s}'",
1055 .{@tagName(int.ptr_ty.ptrAddressSpace(zcu))},
1056 );
1057 }
1058 }
1043 const result_ty_id = try cg.resolveType(int.ptr_ty, .direct);1059 const result_ty_id = try cg.resolveType(int.ptr_ty, .direct);
1044 // TODO: This can probably be an OpSpecConstantOp Bitcast, but1060 // TODO: This can probably be an OpSpecConstantOp Bitcast, but
1045 // that is not implemented by Mesa yet. Therefore, just generate it1061 // that is not implemented by Mesa yet. Therefore, just generate it
...@@ -1137,7 +1153,7 @@ fn constantUavRef(...@@ -1137,7 +1153,7 @@ fn constantUavRef(
1137 // Uav refs are always generic.1153 // Uav refs are always generic.
1138 assert(ty.ptrAddressSpace(zcu) == .generic);1154 assert(ty.ptrAddressSpace(zcu) == .generic);
1139 const uav_ty_id = try cg.resolveType(uav_ty, .indirect);1155 const uav_ty_id = try cg.resolveType(uav_ty, .indirect);
1140 const decl_ptr_ty_id = try cg.module.ptrType(uav_ty_id, .generic);1156 const decl_ptr_ty_id = try cg.module.ptrType(uav_ty_id, .function);
1141 const ptr_id = try cg.resolveUav(uav.val);1157 const ptr_id = try cg.resolveUav(uav.val);
11421158
1143 if (decl_ptr_ty_id != ty_id) {1159 if (decl_ptr_ty_id != ty_id) {
...@@ -1327,7 +1343,10 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {...@@ -1327,7 +1343,10 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
1327 },1343 },
1328 .void => switch (repr) {1344 .void => switch (repr) {
1329 .direct => return try cg.module.voidType(),1345 .direct => return try cg.module.voidType(),
1330 .indirect => return try cg.module.opaqueType("void"),1346 .indirect => {
1347 if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{});
1348 return try cg.module.opaqueType("void");
1349 },
1331 },1350 },
1332 .bool => switch (repr) {1351 .bool => switch (repr) {
1333 .direct => return try cg.module.boolType(),1352 .direct => return try cg.module.boolType(),
...@@ -1337,6 +1356,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {...@@ -1337,6 +1356,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
1337 const int_info = ty.intInfo(zcu);1356 const int_info = ty.intInfo(zcu);
1338 if (int_info.bits == 0) {1357 if (int_info.bits == 0) {
1339 assert(repr == .indirect);1358 assert(repr == .indirect);
1359 if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{});
1340 return try cg.module.opaqueType("u0");1360 return try cg.module.opaqueType("u0");
1341 }1361 }
1342 return try cg.module.intType(int_info.signedness, int_info.bits);1362 return try cg.module.intType(int_info.signedness, int_info.bits);
...@@ -1369,6 +1389,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {...@@ -1369,6 +1389,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
13691389
1370 if (!elem_ty.hasRuntimeBitsIgnoreComptime(zcu)) {1390 if (!elem_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
1371 assert(repr == .indirect);1391 assert(repr == .indirect);
1392 if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{});
1372 return try cg.module.opaqueType("zero-sized-array");1393 return try cg.module.opaqueType("zero-sized-array");
1373 } else if (total_len == 0) {1394 } else if (total_len == 0) {
1374 // The size of the array would be 0, but that is not allowed in SPIR-V.1395 // The size of the array would be 0, but that is not allowed in SPIR-V.
...@@ -1590,6 +1611,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {...@@ -1590,6 +1611,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
1590 return try cg.module.structType(&member_types, &member_names, null, .none);1611 return try cg.module.structType(&member_types, &member_names, null, .none);
1591 },1612 },
1592 .@"opaque" => {1613 .@"opaque" => {
1614 if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{});
1593 const type_name = try cg.resolveTypeName(ty);1615 const type_name = try cg.resolveTypeName(ty);
1594 defer gpa.free(type_name);1616 defer gpa.free(type_name);
1595 return try cg.module.opaqueType(type_name);1617 return try cg.module.opaqueType(type_name);
...@@ -2510,11 +2532,7 @@ fn generateTestEntryPoint(...@@ -2510,11 +2532,7 @@ fn generateTestEntryPoint(
2510 try cg.module.declareEntryPoint(spv_decl_index, test_name, execution_mode, null);2532 try cg.module.declareEntryPoint(spv_decl_index, test_name, execution_mode, null);
2511}2533}
25122534
2513fn intFromBool(cg: *CodeGen, value: Temporary) !Temporary {2535fn intFromBool(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary {
2514 return try cg.intFromBool2(value, Type.u1);
2515}
2516
2517fn intFromBool2(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary {
2518 const zero_id = try cg.constInt(result_ty, 0);2536 const zero_id = try cg.constInt(result_ty, 0);
2519 const one_id = try cg.constInt(result_ty, 1);2537 const one_id = try cg.constInt(result_ty, 1);
25202538
...@@ -2558,7 +2576,7 @@ fn convertToIndirect(cg: *CodeGen, ty: Type, operand_id: Id) !Id {...@@ -2558,7 +2576,7 @@ fn convertToIndirect(cg: *CodeGen, ty: Type, operand_id: Id) !Id {
2558 const zcu = cg.module.zcu;2576 const zcu = cg.module.zcu;
2559 switch (ty.scalarType(zcu).zigTypeTag(zcu)) {2577 switch (ty.scalarType(zcu).zigTypeTag(zcu)) {
2560 .bool => {2578 .bool => {
2561 const result = try cg.intFromBool(Temporary.init(ty, operand_id));2579 const result = try cg.intFromBool(.init(ty, operand_id), .u1);
2562 return try result.materialize(cg);2580 return try result.materialize(cg);
2563 },2581 },
2564 else => return operand_id,2582 else => return operand_id,
...@@ -2958,7 +2976,7 @@ fn normalize(cg: *CodeGen, value: Temporary, info: ArithmeticTypeInfo) !Temporar...@@ -2958,7 +2976,7 @@ fn normalize(cg: *CodeGen, value: Temporary, info: ArithmeticTypeInfo) !Temporar
2958 .composite_integer, .integer, .bool, .float => return value,2976 .composite_integer, .integer, .bool, .float => return value,
2959 .strange_integer => switch (info.signedness) {2977 .strange_integer => switch (info.signedness) {
2960 .unsigned => {2978 .unsigned => {
2961 const mask_value = if (info.bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1;2979 const mask_value = @as(u64, std.math.maxInt(u64)) >> @as(u6, @intCast(64 - info.bits));
2962 const mask_id = try cg.constInt(ty.scalarType(zcu), mask_value);2980 const mask_id = try cg.constInt(ty.scalarType(zcu), mask_value);
2963 return try cg.buildBinary(.OpBitwiseAnd, value, Temporary.init(ty.scalarType(zcu), mask_id));2981 return try cg.buildBinary(.OpBitwiseAnd, value, Temporary.init(ty.scalarType(zcu), mask_id));
2964 },2982 },
...@@ -2997,28 +3015,12 @@ fn airDivFloor(cg: *CodeGen, inst: Air.Inst.Index) !?Id {...@@ -2997,28 +3015,12 @@ fn airDivFloor(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
29973015
2998 const div = try cg.buildBinary(.OpSDiv, lhs, rhs);3016 const div = try cg.buildBinary(.OpSDiv, lhs, rhs);
2999 const rem = try cg.buildBinary(.OpSRem, lhs, rhs);3017 const rem = try cg.buildBinary(.OpSRem, lhs, rhs);
3000
3001 const zero: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, 0));3018 const zero: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, 0));
30023019 const rem_non_zero = try cg.buildCmp(.OpINotEqual, rem, zero);
3003 const rem_is_not_zero = try cg.buildCmp(.OpINotEqual, rem, zero);3020 const lhs_rhs_xor = try cg.buildBinary(.OpBitwiseXor, lhs, rhs);
30043021 const signs_differ = try cg.buildCmp(.OpSLessThan, lhs_rhs_xor, zero);
3005 const result_negative = try cg.buildCmp(3022 const adjust = try cg.buildBinary(.OpLogicalAnd, rem_non_zero, signs_differ);
3006 .OpLogicalNotEqual,3023 const result = try cg.buildBinary(.OpISub, div, try cg.intFromBool(adjust, div.ty));
3007 try cg.buildCmp(.OpSLessThan, lhs, zero),
3008 try cg.buildCmp(.OpSLessThan, rhs, zero),
3009 );
3010 const rem_is_not_zero_and_result_is_negative = try cg.buildBinary(
3011 .OpLogicalAnd,
3012 rem_is_not_zero,
3013 result_negative,
3014 );
3015
3016 const result = try cg.buildBinary(
3017 .OpISub,
3018 div,
3019 try cg.intFromBool2(rem_is_not_zero_and_result_is_negative, div.ty),
3020 );
3021
3022 return try result.materialize(cg);3024 return try result.materialize(cg);
3023 },3025 },
3024 .float => {3026 .float => {
...@@ -3032,10 +3034,8 @@ fn airDivFloor(cg: *CodeGen, inst: Air.Inst.Index) !?Id {...@@ -3032,10 +3034,8 @@ fn airDivFloor(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
30323034
3033fn airDivTrunc(cg: *CodeGen, inst: Air.Inst.Index) !?Id {3035fn airDivTrunc(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3034 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;3036 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3035
3036 const lhs = try cg.temporary(bin_op.lhs);3037 const lhs = try cg.temporary(bin_op.lhs);
3037 const rhs = try cg.temporary(bin_op.rhs);3038 const rhs = try cg.temporary(bin_op.rhs);
3038
3039 const info = cg.arithmeticTypeInfo(lhs.ty);3039 const info = cg.arithmeticTypeInfo(lhs.ty);
3040 switch (info.class) {3040 switch (info.class) {
3041 .composite_integer => unreachable, // TODO3041 .composite_integer => unreachable, // TODO
...@@ -3073,12 +3073,9 @@ fn airArithOp(...@@ -3073,12 +3073,9 @@ fn airArithOp(
3073 comptime uop: Opcode,3073 comptime uop: Opcode,
3074) !?Id {3074) !?Id {
3075 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;3075 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3076
3077 const lhs = try cg.temporary(bin_op.lhs);3076 const lhs = try cg.temporary(bin_op.lhs);
3078 const rhs = try cg.temporary(bin_op.rhs);3077 const rhs = try cg.temporary(bin_op.rhs);
3079
3080 const info = cg.arithmeticTypeInfo(lhs.ty);3078 const info = cg.arithmeticTypeInfo(lhs.ty);
3081
3082 const result = switch (info.class) {3079 const result = switch (info.class) {
3083 .composite_integer => unreachable, // TODO3080 .composite_integer => unreachable, // TODO
3084 .integer, .strange_integer => switch (info.signedness) {3081 .integer, .strange_integer => switch (info.signedness) {
...@@ -3088,7 +3085,6 @@ fn airArithOp(...@@ -3088,7 +3085,6 @@ fn airArithOp(
3088 .float => try cg.buildBinary(fop, lhs, rhs),3085 .float => try cg.buildBinary(fop, lhs, rhs),
3089 .bool => unreachable,3086 .bool => unreachable,
3090 };3087 };
3091
3092 return try result.materialize(cg);3088 return try result.materialize(cg);
3093}3089}
30943090
...@@ -3105,12 +3101,10 @@ fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary {...@@ -3105,12 +3101,10 @@ fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary {
3105 const zcu = cg.module.zcu;3101 const zcu = cg.module.zcu;
3106 const target = cg.module.zcu.getTarget();3102 const target = cg.module.zcu.getTarget();
3107 const operand_info = cg.arithmeticTypeInfo(value.ty);3103 const operand_info = cg.arithmeticTypeInfo(value.ty);
3108
3109 switch (operand_info.class) {3104 switch (operand_info.class) {
3110 .float => return try cg.buildUnary(.f_abs, value),3105 .float => return try cg.buildUnary(.f_abs, value),
3111 .integer, .strange_integer => {3106 .integer, .strange_integer => {
3112 const abs_value = try cg.buildUnary(.i_abs, value);3107 const abs_value = try cg.buildUnary(.i_abs, value);
3113
3114 switch (target.os.tag) {3108 switch (target.os.tag) {
3115 .vulkan, .opengl => {3109 .vulkan, .opengl => {
3116 if (value.ty.intInfo(zcu).signedness == .signed) {3110 if (value.ty.intInfo(zcu).signedness == .signed) {
...@@ -3119,7 +3113,6 @@ fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary {...@@ -3119,7 +3113,6 @@ fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary {
3119 },3113 },
3120 else => {},3114 else => {},
3121 }3115 }
3122
3123 return try cg.normalize(abs_value, cg.arithmeticTypeInfo(result_ty));3116 return try cg.normalize(abs_value, cg.arithmeticTypeInfo(result_ty));
3124 },3117 },
3125 .composite_integer => unreachable, // TODO3118 .composite_integer => unreachable, // TODO
...@@ -3134,19 +3127,18 @@ fn airAddSubOverflow(...@@ -3134,19 +3127,18 @@ fn airAddSubOverflow(
3134 u_opcode: Opcode,3127 u_opcode: Opcode,
3135 s_opcode: Opcode,3128 s_opcode: Opcode,
3136) !?Id {3129) !?Id {
3137 _ = s_opcode;
3138 // Note: OpIAddCarry and OpISubBorrow are not really useful here: For unsigned numbers,3130 // Note: OpIAddCarry and OpISubBorrow are not really useful here: For unsigned numbers,
3139 // there is in both cases only one extra operation required. For signed operations,3131 // there is in both cases only one extra operation required. For signed operations,
3140 // the overflow bit is set then going from 0x80.. to 0x00.., but this doesn't actually3132 // the overflow bit is set then going from 0x80.. to 0x00.., but this doesn't actually
3141 // normally set a carry bit. So the SPIR-V overflow operations are not particularly3133 // normally set a carry bit. So the SPIR-V overflow operations are not particularly
3142 // useful here.3134 // useful here.
31433135
3136 _ = s_opcode;
3137
3144 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;3138 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3145 const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data;3139 const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data;
3146
3147 const lhs = try cg.temporary(extra.lhs);3140 const lhs = try cg.temporary(extra.lhs);
3148 const rhs = try cg.temporary(extra.rhs);3141 const rhs = try cg.temporary(extra.rhs);
3149
3150 const result_ty = cg.typeOfIndex(inst);3142 const result_ty = cg.typeOfIndex(inst);
31513143
3152 const info = cg.arithmeticTypeInfo(lhs.ty);3144 const info = cg.arithmeticTypeInfo(lhs.ty);
...@@ -3158,7 +3150,6 @@ fn airAddSubOverflow(...@@ -3158,7 +3150,6 @@ fn airAddSubOverflow(
31583150
3159 const sum = try cg.buildBinary(add, lhs, rhs);3151 const sum = try cg.buildBinary(add, lhs, rhs);
3160 const result = try cg.normalize(sum, info);3152 const result = try cg.normalize(sum, info);
3161
3162 const overflowed = switch (info.signedness) {3153 const overflowed = switch (info.signedness) {
3163 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which.3154 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which.
3164 // For subtraction the conditions need to be swapped.3155 // For subtraction the conditions need to be swapped.
...@@ -3173,38 +3164,31 @@ fn airAddSubOverflow(...@@ -3173,38 +3164,31 @@ fn airAddSubOverflow(
3173 // and the result's sign is different from the minuend's (a's) sign.3164 // and the result's sign is different from the minuend's (a's) sign.
3174 // (sign(a) != sign(b)) && (sign(a) != sign(result))3165 // (sign(a) != sign(b)) && (sign(a) != sign(result))
3175 const zero: Temporary = .init(rhs.ty, try cg.constInt(rhs.ty, 0));3166 const zero: Temporary = .init(rhs.ty, try cg.constInt(rhs.ty, 0));
3176
3177 const lhs_is_neg = try cg.buildCmp(.OpSLessThan, lhs, zero);3167 const lhs_is_neg = try cg.buildCmp(.OpSLessThan, lhs, zero);
3178 const rhs_is_neg = try cg.buildCmp(.OpSLessThan, rhs, zero);3168 const rhs_is_neg = try cg.buildCmp(.OpSLessThan, rhs, zero);
3179 const result_is_neg = try cg.buildCmp(.OpSLessThan, result, zero);3169 const result_is_neg = try cg.buildCmp(.OpSLessThan, result, zero);
3180
3181 const signs_match = try cg.buildCmp(.OpLogicalEqual, lhs_is_neg, rhs_is_neg);3170 const signs_match = try cg.buildCmp(.OpLogicalEqual, lhs_is_neg, rhs_is_neg);
3182 const result_sign_differs = try cg.buildCmp(.OpLogicalNotEqual, lhs_is_neg, result_is_neg);3171 const result_sign_differs = try cg.buildCmp(.OpLogicalNotEqual, lhs_is_neg, result_is_neg);
31833172 const overflow_condition = switch (add) {
3184 const overflow_condition = if (add == .OpIAdd)3173 .OpIAdd => signs_match,
3185 signs_match3174 .OpISub => try cg.buildUnary(.l_not, signs_match),
3186 else // .OpISub3175 else => unreachable,
3187 try cg.buildUnary(.l_not, signs_match);3176 };
3188
3189 break :blk try cg.buildCmp(.OpLogicalAnd, overflow_condition, result_sign_differs);3177 break :blk try cg.buildCmp(.OpLogicalAnd, overflow_condition, result_sign_differs);
3190 },3178 },
3191 };3179 };
31923180
3193 const ov = try cg.intFromBool(overflowed);3181 const ov = try cg.intFromBool(overflowed, .u1);
3194
3195 const result_ty_id = try cg.resolveType(result_ty, .direct);3182 const result_ty_id = try cg.resolveType(result_ty, .direct);
3196 return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) });3183 return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) });
3197}3184}
31983185
3199fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id {3186fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3200 const pt = cg.pt;3187 const pt = cg.pt;
3201
3202 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;3188 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3203 const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data;3189 const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data;
3204
3205 const lhs = try cg.temporary(extra.lhs);3190 const lhs = try cg.temporary(extra.lhs);
3206 const rhs = try cg.temporary(extra.rhs);3191 const rhs = try cg.temporary(extra.rhs);
3207
3208 const result_ty = cg.typeOfIndex(inst);3192 const result_ty = cg.typeOfIndex(inst);
32093193
3210 const info = cg.arithmeticTypeInfo(lhs.ty);3194 const info = cg.arithmeticTypeInfo(lhs.ty);
...@@ -3237,20 +3221,15 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id {...@@ -3237,20 +3221,15 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3237 const op_ty = try pt.intType(.unsigned, op_ty_bits);3221 const op_ty = try pt.intType(.unsigned, op_ty_bits);
3238 const casted_lhs = try cg.buildConvert(op_ty, lhs);3222 const casted_lhs = try cg.buildConvert(op_ty, lhs);
3239 const casted_rhs = try cg.buildConvert(op_ty, rhs);3223 const casted_rhs = try cg.buildConvert(op_ty, rhs);
3240
3241 const full_result = try cg.buildBinary(.OpIMul, casted_lhs, casted_rhs);3224 const full_result = try cg.buildBinary(.OpIMul, casted_lhs, casted_rhs);
3242
3243 const low_bits = try cg.buildConvert(lhs.ty, full_result);3225 const low_bits = try cg.buildConvert(lhs.ty, full_result);
3244 const result = try cg.normalize(low_bits, info);3226 const result = try cg.normalize(low_bits, info);
3245
3246 // Shift the result bits away to get the overflow bits.3227 // Shift the result bits away to get the overflow bits.
3247 const shift: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, info.bits));3228 const shift: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, info.bits));
3248 const overflow = try cg.buildBinary(.OpShiftRightLogical, full_result, shift);3229 const overflow = try cg.buildBinary(.OpShiftRightLogical, full_result, shift);
3249
3250 // Directly check if its zero in the op_ty without converting first.3230 // Directly check if its zero in the op_ty without converting first.
3251 const zero: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, 0));3231 const zero: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, 0));
3252 const overflowed = try cg.buildCmp(.OpINotEqual, zero, overflow);3232 const overflowed = try cg.buildCmp(.OpINotEqual, zero, overflow);
3253
3254 break :blk .{ result, overflowed };3233 break :blk .{ result, overflowed };
3255 }3234 }
32563235
...@@ -3362,7 +3341,7 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id {...@@ -3362,7 +3341,7 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3362 },3341 },
3363 };3342 };
33643343
3365 const ov = try cg.intFromBool(overflowed);3344 const ov = try cg.intFromBool(overflowed, .u1);
33663345
3367 const result_ty_id = try cg.resolveType(result_ty, .direct);3346 const result_ty_id = try cg.resolveType(result_ty, .direct);
3368 return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) });3347 return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) });
...@@ -3403,7 +3382,7 @@ fn airShlOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id {...@@ -3403,7 +3382,7 @@ fn airShlOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3403 };3382 };
34043383
3405 const overflowed = try cg.buildCmp(.OpINotEqual, base, right);3384 const overflowed = try cg.buildCmp(.OpINotEqual, base, right);
3406 const ov = try cg.intFromBool(overflowed);3385 const ov = try cg.intFromBool(overflowed, .u1);
34073386
3408 const result_ty_id = try cg.resolveType(result_ty, .direct);3387 const result_ty_id = try cg.resolveType(result_ty, .direct);
3409 return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) });3388 return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) });
...@@ -3931,6 +3910,7 @@ fn bitCast(...@@ -3931,6 +3910,7 @@ fn bitCast(
3931) !Id {3910) !Id {
3932 const gpa = cg.module.gpa;3911 const gpa = cg.module.gpa;
3933 const zcu = cg.module.zcu;3912 const zcu = cg.module.zcu;
3913 const target = zcu.getTarget();
3934 const src_ty_id = try cg.resolveType(src_ty, .direct);3914 const src_ty_id = try cg.resolveType(src_ty, .direct);
3935 const dst_ty_id = try cg.resolveType(dst_ty, .direct);3915 const dst_ty_id = try cg.resolveType(dst_ty, .direct);
39363916
...@@ -3941,6 +3921,15 @@ fn bitCast(...@@ -3941,6 +3921,15 @@ fn bitCast(
3941 // See fn bitCast in llvm.zig3921 // See fn bitCast in llvm.zig
39423922
3943 if (src_ty.zigTypeTag(zcu) == .int and dst_ty.isPtrAtRuntime(zcu)) {3923 if (src_ty.zigTypeTag(zcu) == .int and dst_ty.isPtrAtRuntime(zcu)) {
3924 if (target.os.tag != .opencl) {
3925 if (dst_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) {
3926 return cg.fail(
3927 "cannot cast integer to pointer with address space '{s}'",
3928 .{@tagName(dst_ty.ptrAddressSpace(zcu))},
3929 );
3930 }
3931 }
3932
3944 const result_id = cg.module.allocId();3933 const result_id = cg.module.allocId();
3945 try cg.body.emit(gpa, .OpConvertUToPtr, .{3934 try cg.body.emit(gpa, .OpConvertUToPtr, .{
3946 .id_result_type = dst_ty_id,3935 .id_result_type = dst_ty_id,
...@@ -3967,7 +3956,8 @@ fn bitCast(...@@ -3967,7 +3956,8 @@ fn bitCast(
39673956
3968 const dst_ptr_ty_id = try cg.module.ptrType(dst_ty_id, .function);3957 const dst_ptr_ty_id = try cg.module.ptrType(dst_ty_id, .function);
39693958
3970 const tmp_id = try cg.alloc(src_ty, .{ .storage_class = .function });3959 const src_ty_indirect_id = try cg.resolveType(src_ty, .indirect);
3960 const tmp_id = try cg.alloc(src_ty_indirect_id, null);
3971 try cg.store(src_ty, tmp_id, src_id, .{});3961 try cg.store(src_ty, tmp_id, src_id, .{});
3972 const casted_ptr_id = cg.module.allocId();3962 const casted_ptr_id = cg.module.allocId();
3973 try cg.body.emit(gpa, .OpBitcast, .{3963 try cg.body.emit(gpa, .OpBitcast, .{
...@@ -3997,7 +3987,7 @@ fn airBitCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id {...@@ -3997,7 +3987,7 @@ fn airBitCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3997 const result_ty = cg.typeOfIndex(inst);3987 const result_ty = cg.typeOfIndex(inst);
3998 if (operand_ty.toIntern() == .bool_type) {3988 if (operand_ty.toIntern() == .bool_type) {
3999 const operand = try cg.temporary(ty_op.operand);3989 const operand = try cg.temporary(ty_op.operand);
4000 const result = try cg.intFromBool(operand);3990 const result = try cg.intFromBool(operand, .u1);
4001 return try result.materialize(cg);3991 return try result.materialize(cg);
4002 }3992 }
4003 const operand_id = try cg.resolve(ty_op.operand);3993 const operand_id = try cg.resolve(ty_op.operand);
...@@ -4420,7 +4410,6 @@ fn airArrayElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id {...@@ -4420,7 +4410,6 @@ fn airArrayElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4420 // TODO: This backend probably also should use isByRef from llvm...4410 // TODO: This backend probably also should use isByRef from llvm...
44214411
4422 const is_vector = array_ty.isVector(zcu);4412 const is_vector = array_ty.isVector(zcu);
4423
4424 const elem_repr: Repr = if (is_vector) .direct else .indirect;4413 const elem_repr: Repr = if (is_vector) .direct else .indirect;
4425 const array_ty_id = try cg.resolveType(array_ty, .direct);4414 const array_ty_id = try cg.resolveType(array_ty, .direct);
4426 const elem_ty_id = try cg.resolveType(elem_ty, elem_repr);4415 const elem_ty_id = try cg.resolveType(elem_ty, elem_repr);
...@@ -4588,7 +4577,8 @@ fn unionInit(...@@ -4588,7 +4577,8 @@ fn unionInit(
4588 return try cg.constInt(tag_ty, tag_int);4577 return try cg.constInt(tag_ty, tag_int);
4589 }4578 }
45904579
4591 const tmp_id = try cg.alloc(ty, .{ .storage_class = .function });4580 const ty_id = try cg.resolveType(ty, .indirect);
4581 const tmp_id = try cg.alloc(ty_id, null);
45924582
4593 if (layout.tag_size != 0) {4583 if (layout.tag_size != 0) {
4594 const tag_ty_id = try cg.resolveType(tag_ty, .indirect);4584 const tag_ty_id = try cg.resolveType(tag_ty, .indirect);
...@@ -4709,7 +4699,8 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id {...@@ -4709,7 +4699,8 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4709 const layout = cg.unionLayout(object_ty);4699 const layout = cg.unionLayout(object_ty);
4710 assert(layout.has_payload);4700 assert(layout.has_payload);
47114701
4712 const tmp_id = try cg.alloc(object_ty, .{ .storage_class = .function });4702 const object_ty_id = try cg.resolveType(object_ty, .indirect);
4703 const tmp_id = try cg.alloc(object_ty_id, null);
4713 try cg.store(object_ty, tmp_id, object_id, .{});4704 try cg.store(object_ty, tmp_id, object_id, .{});
47144705
4715 const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect);4706 const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect);
...@@ -4733,13 +4724,16 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id {...@@ -4733,13 +4724,16 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
47334724
4734fn airFieldParentPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id {4725fn airFieldParentPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4735 const zcu = cg.module.zcu;4726 const zcu = cg.module.zcu;
4727 const target = zcu.getTarget();
4736 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;4728 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4737 const extra = cg.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;4729 const extra = cg.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
47384730
4739 const parent_ty = ty_pl.ty.toType().childType(zcu);4731 const parent_ptr_ty = ty_pl.ty.toType();
4740 const result_ty_id = try cg.resolveType(ty_pl.ty.toType(), .indirect);4732 const parent_ty = parent_ptr_ty.childType(zcu);
4733 const result_ty_id = try cg.resolveType(parent_ptr_ty, .indirect);
47414734
4742 const field_ptr = try cg.resolve(extra.field_ptr);4735 const field_ptr = try cg.resolve(extra.field_ptr);
4736 const field_ptr_ty = cg.typeOf(extra.field_ptr);
4743 const field_ptr_int = try cg.intFromPtr(field_ptr);4737 const field_ptr_int = try cg.intFromPtr(field_ptr);
4744 const field_offset = parent_ty.structFieldOffset(extra.field_index, zcu);4738 const field_offset = parent_ty.structFieldOffset(extra.field_index, zcu);
47454739
...@@ -4753,6 +4747,15 @@ fn airFieldParentPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id {...@@ -4753,6 +4747,15 @@ fn airFieldParentPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4753 break :base_ptr_int try result.materialize(cg);4747 break :base_ptr_int try result.materialize(cg);
4754 };4748 };
47554749
4750 if (target.os.tag != .opencl) {
4751 if (field_ptr_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) {
4752 return cg.fail(
4753 "cannot cast integer to pointer with address space '{s}'",
4754 .{@tagName(field_ptr_ty.ptrAddressSpace(zcu))},
4755 );
4756 }
4757 }
4758
4756 const base_ptr = cg.module.allocId();4759 const base_ptr = cg.module.allocId();
4757 try cg.body.emit(cg.module.gpa, .OpConvertUToPtr, .{4760 try cg.body.emit(cg.module.gpa, .OpConvertUToPtr, .{
4758 .id_result_type = result_ty_id,4761 .id_result_type = result_ty_id,
...@@ -4821,46 +4824,33 @@ fn airStructFieldPtrIndex(cg: *CodeGen, inst: Air.Inst.Index, field_index: u32)...@@ -4821,46 +4824,33 @@ fn airStructFieldPtrIndex(cg: *CodeGen, inst: Air.Inst.Index, field_index: u32)
4821 return try cg.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index);4824 return try cg.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index);
4822}4825}
48234826
4824const AllocOptions = struct {4827fn alloc(cg: *CodeGen, ty_id: Id, initializer: ?Id) !Id {
4825 initializer: ?Id = null,4828 const ptr_ty_id = try cg.module.ptrType(ty_id, .function);
4826 /// The final storage class of the pointer. This may be either `.Generic` or `.Function`.4829 const result_id = cg.module.allocId();
4827 /// In either case, the local is allocated in the `.Function` storage class, and optionally
4828 /// cast back to `.Generic`.
4829 storage_class: StorageClass,
4830};
4831
4832// Allocate a function-local variable, with possible initializer.
4833// This function returns a pointer to a variable of type `ty`,
4834// which is in the Generic address space. The variable is actually
4835// placed in the Function address space.
4836fn alloc(
4837 cg: *CodeGen,
4838 ty: Type,
4839 options: AllocOptions,
4840) !Id {
4841 const ty_id = try cg.resolveType(ty, .indirect);
4842 const ptr_fn_ty_id = try cg.module.ptrType(ty_id, .function);
4843
4844 // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to
4845 // directly generate them into func.prologue instead of the body.
4846 const var_id = cg.module.allocId();
4847 try cg.prologue.emit(cg.module.gpa, .OpVariable, .{4830 try cg.prologue.emit(cg.module.gpa, .OpVariable, .{
4848 .id_result_type = ptr_fn_ty_id,4831 .id_result_type = ptr_ty_id,
4849 .id_result = var_id,4832 .id_result = result_id,
4850 .storage_class = .function,4833 .storage_class = .function,
4851 .initializer = options.initializer,4834 .initializer = initializer,
4852 });4835 });
48534836 return result_id;
4854 return var_id;
4855}4837}
48564838
4857fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) !?Id {4839fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4858 const zcu = cg.module.zcu;4840 const zcu = cg.module.zcu;
4841 const target = zcu.getTarget();
4859 const ptr_ty = cg.typeOfIndex(inst);4842 const ptr_ty = cg.typeOfIndex(inst);
4860 const child_ty = ptr_ty.childType(zcu);4843 const child_ty = ptr_ty.childType(zcu);
4861 return try cg.alloc(child_ty, .{4844 const child_ty_id = try cg.resolveType(child_ty, .indirect);
4862 .storage_class = cg.module.storageClass(ptr_ty.ptrAddressSpace(zcu)),4845 const ptr_align = ptr_ty.ptrAlignment(zcu);
4863 });4846 const result_id = try cg.alloc(child_ty_id, null);
4847 if (ptr_align != child_ty.abiAlignment(zcu)) {
4848 if (target.os.tag != .opencl) return cg.fail("cannot apply alignment to variables", .{});
4849 try cg.module.decorate(result_id, .{
4850 .alignment = .{ .alignment = @intCast(ptr_align.toByteUnits().?) },
4851 });
4852 }
4853 return result_id;
4864}4854}
48654855
4866fn airArg(cg: *CodeGen) Id {4856fn airArg(cg: *CodeGen) Id {
...@@ -5087,7 +5077,8 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, body: []const Air.Inst.Index)...@@ -5087,7 +5077,8 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, body: []const Air.Inst.Index)
5087 };5077 };
50885078
5089 const maybe_block_result_var_id = if (have_block_result) blk: {5079 const maybe_block_result_var_id = if (have_block_result) blk: {
5090 const block_result_var_id = try cg.alloc(ty, .{ .storage_class = .function });5080 const ty_id = try cg.resolveType(ty, .indirect);
5081 const block_result_var_id = try cg.alloc(ty_id, null);
5091 try cf.block_results.putNoClobber(gpa, inst, block_result_var_id);5082 try cf.block_results.putNoClobber(gpa, inst, block_result_var_id);
5092 break :blk block_result_var_id;5083 break :blk block_result_var_id;
5093 } else null;5084 } else null;
src/codegen/spirv/Module.zig+6-5
...@@ -676,8 +676,9 @@ pub fn structType(...@@ -676,8 +676,9 @@ pub fn structType(
676 ip_index: InternPool.Index,676 ip_index: InternPool.Index,
677) !Id {677) !Id {
678 const target = module.zcu.getTarget();678 const target = module.zcu.getTarget();
679 const actual_ip_index = if (module.zcu.comp.config.root_strip) .none else ip_index;
679680
680 if (module.cache.struct_types.get(.{ .fields = types, .ip_index = ip_index })) |id| return id;681 if (module.cache.struct_types.get(.{ .fields = types, .ip_index = actual_ip_index })) |id| return id;
681 const result_id = module.allocId();682 const result_id = module.allocId();
682 const types_dup = try module.arena.dupe(Id, types);683 const types_dup = try module.arena.dupe(Id, types);
683 try module.sections.globals.emit(module.gpa, .OpTypeStruct, .{684 try module.sections.globals.emit(module.gpa, .OpTypeStruct, .{
...@@ -710,10 +711,7 @@ pub fn structType(...@@ -710,10 +711,7 @@ pub fn structType(
710711
711 try module.cache.struct_types.put(712 try module.cache.struct_types.put(
712 module.gpa,713 module.gpa,
713 .{714 .{ .fields = types_dup, .ip_index = actual_ip_index },
714 .fields = types_dup,
715 .ip_index = if (module.zcu.comp.config.root_strip) .none else ip_index,
716 },
717 result_id,715 result_id,
718 );716 );
719 return result_id;717 return result_id;
...@@ -874,6 +872,7 @@ pub fn declareEntryPoint(...@@ -874,6 +872,7 @@ pub fn declareEntryPoint(
874}872}
875873
876pub fn debugName(module: *Module, target: Id, name: []const u8) !void {874pub fn debugName(module: *Module, target: Id, name: []const u8) !void {
875 if (module.zcu.comp.config.root_strip) return;
877 try module.sections.debug_names.emit(module.gpa, .OpName, .{876 try module.sections.debug_names.emit(module.gpa, .OpName, .{
878 .target = target,877 .target = target,
879 .name = name,878 .name = name,
...@@ -881,12 +880,14 @@ pub fn debugName(module: *Module, target: Id, name: []const u8) !void {...@@ -881,12 +880,14 @@ pub fn debugName(module: *Module, target: Id, name: []const u8) !void {
881}880}
882881
883pub fn debugNameFmt(module: *Module, target: Id, comptime fmt: []const u8, args: anytype) !void {882pub fn debugNameFmt(module: *Module, target: Id, comptime fmt: []const u8, args: anytype) !void {
883 if (module.zcu.comp.config.root_strip) return;
884 const name = try std.fmt.allocPrint(module.gpa, fmt, args);884 const name = try std.fmt.allocPrint(module.gpa, fmt, args);
885 defer module.gpa.free(name);885 defer module.gpa.free(name);
886 try module.debugName(target, name);886 try module.debugName(target, name);
887}887}
888888
889pub fn memberDebugName(module: *Module, target: Id, member: u32, name: []const u8) !void {889pub fn memberDebugName(module: *Module, target: Id, member: u32, name: []const u8) !void {
890 if (module.zcu.comp.config.root_strip) return;
890 try module.sections.debug_names.emit(module.gpa, .OpMemberName, .{891 try module.sections.debug_names.emit(module.gpa, .OpMemberName, .{
891 .type = target,892 .type = target,
892 .member = member,893 .member = member,
src/link/SpirV.zig+4-11
...@@ -10,10 +10,11 @@ const Compilation = @import("../Compilation.zig");...@@ -10,10 +10,11 @@ const Compilation = @import("../Compilation.zig");
10const link = @import("../link.zig");10const link = @import("../link.zig");
11const Air = @import("../Air.zig");11const Air = @import("../Air.zig");
12const Type = @import("../Type.zig");12const Type = @import("../Type.zig");
13const BinaryModule = @import("SpirV/BinaryModule.zig");
14const CodeGen = @import("../codegen/spirv/CodeGen.zig");13const CodeGen = @import("../codegen/spirv/CodeGen.zig");
15const Module = @import("../codegen/spirv/Module.zig");14const Module = @import("../codegen/spirv/Module.zig");
16const trace = @import("../tracy.zig").trace;15const trace = @import("../tracy.zig").trace;
16const BinaryModule = @import("SpirV/BinaryModule.zig");
17const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
1718
18const spec = @import("../codegen/spirv/spec.zig");19const spec = @import("../codegen/spirv/spec.zig");
19const Id = spec.Id;20const Id = spec.Id;
...@@ -279,7 +280,7 @@ pub fn flush(...@@ -279,7 +280,7 @@ pub fn flush(
279 const module = try linker.module.finalize(arena);280 const module = try linker.module.finalize(arena);
280 errdefer arena.free(module);281 errdefer arena.free(module);
281282
282 const linked_module = linker.linkModule(arena, module, sub_prog_node) catch |err| switch (err) {283 const linked_module = linkModule(arena, module, sub_prog_node) catch |err| switch (err) {
283 error.OutOfMemory => return error.OutOfMemory,284 error.OutOfMemory => return error.OutOfMemory,
284 else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}),285 else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}),
285 };286 };
...@@ -288,18 +289,10 @@ pub fn flush(...@@ -288,18 +289,10 @@ pub fn flush(
288 return diags.fail("failed to write: {s}", .{@errorName(err)});289 return diags.fail("failed to write: {s}", .{@errorName(err)});
289}290}
290291
291fn linkModule(linker: *Linker, arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {292fn linkModule(arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {
292 _ = linker;
293
294 const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
295 const prune_unused = @import("SpirV/prune_unused.zig");
296
297 var parser = try BinaryModule.Parser.init(arena);293 var parser = try BinaryModule.Parser.init(arena);
298 defer parser.deinit();294 defer parser.deinit();
299 var binary = try parser.parse(module);295 var binary = try parser.parse(module);
300
301 try lower_invocation_globals.run(&parser, &binary, progress);296 try lower_invocation_globals.run(&parser, &binary, progress);
302 try prune_unused.run(&parser, &binary, progress);
303
304 return binary.finalize(arena);297 return binary.finalize(arena);
305}298}
src/link/SpirV/lower_invocation_globals.zig+10-1
...@@ -382,6 +382,15 @@ const ModuleBuilder = struct {...@@ -382,6 +382,15 @@ const ModuleBuilder = struct {
382 var it = binary.iterateInstructions();382 var it = binary.iterateInstructions();
383 while (it.next()) |inst| {383 while (it.next()) |inst| {
384 switch (inst.opcode) {384 switch (inst.opcode) {
385 .OpName => {
386 const id: ResultId = @enumFromInt(inst.operands[0]);
387 if (info.invocation_globals.contains(id)) continue;
388 },
389 .OpExtInstImport => {
390 const set_id: ResultId = @enumFromInt(inst.operands[0]);
391 const set = binary.ext_inst_map.get(set_id).?;
392 if (set == .zig) continue;
393 },
385 .OpExtInst => {394 .OpExtInst => {
386 const set_id: ResultId = @enumFromInt(inst.operands[2]);395 const set_id: ResultId = @enumFromInt(inst.operands[2]);
387 const set_inst = inst.operands[3];396 const set_inst = inst.operands[3];
...@@ -482,7 +491,7 @@ const ModuleBuilder = struct {...@@ -482,7 +491,7 @@ const ModuleBuilder = struct {
482 return entry.value_ptr.*;491 return entry.value_ptr.*;
483 }492 }
484493
485 /// Rewrite the modules' functions and emit them with the new parameter types.494 /// Rewrite the modules functions and emit them with the new parameter types.
486 fn rewriteFunctions(495 fn rewriteFunctions(
487 self: *ModuleBuilder,496 self: *ModuleBuilder,
488 parser: *BinaryModule.Parser,497 parser: *BinaryModule.Parser,
src/link/SpirV/prune_unused.zig deleted-362
...@@ -1,362 +0,0 @@
1//! This pass is used to simple pruning of unused things:
2//! - Instructions at global scope
3//! - Functions
4//! Debug info and nonsemantic instructions are not handled;
5//! this pass is mainly intended for cleaning up left over
6//! stuff from codegen and other passes that is generated
7//! but not actually used.
8
9const std = @import("std");
10const Allocator = std.mem.Allocator;
11const assert = std.debug.assert;
12const log = std.log.scoped(.spirv_link);
13
14const BinaryModule = @import("BinaryModule.zig");
15const Section = @import("../../codegen/spirv/Section.zig");
16const spec = @import("../../codegen/spirv/spec.zig");
17const Opcode = spec.Opcode;
18const ResultId = spec.Id;
19const Word = spec.Word;
20
21/// Return whether a particular opcode's instruction can be pruned.
22/// These are idempotent instructions at globals scope and instructions
23/// within functions that do not have any side effects.
24/// The opcodes that return true here do not necessarily need to
25/// have an .Id. If they don't, then they are regarded
26/// as 'decoration'-style instructions that don't keep their
27/// operands alive, but will be emitted if they are.
28fn canPrune(op: Opcode) bool {
29 // This list should be as worked out as possible, but just
30 // getting common instructions is a good effort/effect ratio.
31 // When adding items to this list, also check whether the
32 // instruction requires any special control flow rules (like
33 // with labels and control flow and stuff) and whether the
34 // instruction has any non-trivial side effects (like OpLoad
35 // with the Volatile memory semantics).
36 return switch (op.class()) {
37 .type_declaration,
38 .conversion,
39 .arithmetic,
40 .relational_and_logical,
41 .bit,
42 .annotation,
43 => true,
44 else => switch (op) {
45 .OpFunction,
46 .OpUndef,
47 .OpString,
48 .OpName,
49 .OpMemberName,
50 // Prune OpConstant* instructions but
51 // retain OpSpecConstant declaration instructions
52 .OpConstantTrue,
53 .OpConstantFalse,
54 .OpConstant,
55 .OpConstantComposite,
56 .OpConstantSampler,
57 .OpConstantNull,
58 .OpSpecConstantOp,
59 // Prune ext inst import instructions, but not
60 // ext inst instructions themselves, because
61 // we don't know if they might have side effects.
62 .OpExtInstImport,
63 => true,
64 else => false,
65 },
66 };
67}
68
69const ModuleInfo = struct {
70 const Fn = struct {
71 /// The index of the first callee in `callee_store`.
72 first_callee: usize,
73 };
74
75 /// Maps function result-id -> Fn information structure.
76 functions: std.AutoArrayHashMapUnmanaged(ResultId, Fn),
77 /// For each function, a list of function result-ids that it calls.
78 callee_store: []const ResultId,
79 /// For each instruction, the offset at which it appears in the source module.
80 result_id_to_code_offset: std.AutoArrayHashMapUnmanaged(ResultId, usize),
81
82 /// Fetch the list of callees per function. Guaranteed to contain only unique IDs.
83 fn callees(self: ModuleInfo, fn_id: ResultId) []const ResultId {
84 const fn_index = self.functions.getIndex(fn_id).?;
85 const values = self.functions.values();
86 const first_callee = values[fn_index].first_callee;
87 if (fn_index == values.len - 1) {
88 return self.callee_store[first_callee..];
89 } else {
90 const next_first_callee = values[fn_index + 1].first_callee;
91 return self.callee_store[first_callee..next_first_callee];
92 }
93 }
94
95 /// Extract the information required to run this pass from the binary.
96 // TODO: Should the contents of this function be merged with that of lower_invocation_globals.zig?
97 // Many of the contents are the same...
98 fn parse(
99 arena: Allocator,
100 parser: *BinaryModule.Parser,
101 binary: BinaryModule,
102 ) !ModuleInfo {
103 var functions = std.AutoArrayHashMap(ResultId, Fn).init(arena);
104 var calls = std.AutoArrayHashMap(ResultId, void).init(arena);
105 var callee_store = std.ArrayList(ResultId).init(arena);
106 var result_id_to_code_offset = std.AutoArrayHashMap(ResultId, usize).init(arena);
107 var maybe_current_function: ?ResultId = null;
108 var it = binary.iterateInstructions();
109 while (it.next()) |inst| {
110 const inst_spec = parser.getInstSpec(inst.opcode).?;
111
112 // Result-id can only be the first or second operand
113 const maybe_result_id: ?ResultId = for (0..2) |i| {
114 if (inst_spec.operands.len > i and inst_spec.operands[i].kind == .id_result) {
115 break @enumFromInt(inst.operands[i]);
116 }
117 } else null;
118
119 // Only add result-ids of functions and anything outside a function.
120 // Result-ids declared inside functions cannot be reached outside anyway,
121 // and we don't care about the internals of functions anyway.
122 // Note that in the case of OpFunction, `maybe_current_function` is
123 // also `null`, because it is set below.
124 if (maybe_result_id) |result_id| {
125 try result_id_to_code_offset.put(result_id, inst.offset);
126 }
127
128 switch (inst.opcode) {
129 .OpFunction => {
130 if (maybe_current_function) |current_function| {
131 log.err("OpFunction {f} does not have an OpFunctionEnd", .{current_function});
132 return error.InvalidPhysicalFormat;
133 }
134
135 maybe_current_function = @enumFromInt(inst.operands[1]);
136 },
137 .OpFunctionCall => {
138 const callee: ResultId = @enumFromInt(inst.operands[2]);
139 try calls.put(callee, {});
140 },
141 .OpFunctionEnd => {
142 const current_function = maybe_current_function orelse {
143 log.err("encountered OpFunctionEnd without corresponding OpFunction", .{});
144 return error.InvalidPhysicalFormat;
145 };
146 const entry = try functions.getOrPut(current_function);
147 if (entry.found_existing) {
148 log.err("Function {f} has duplicate definition", .{current_function});
149 return error.DuplicateId;
150 }
151
152 const first_callee = callee_store.items.len;
153 try callee_store.appendSlice(calls.keys());
154
155 entry.value_ptr.* = .{
156 .first_callee = first_callee,
157 };
158 maybe_current_function = null;
159 calls.clearRetainingCapacity();
160 },
161 else => {},
162 }
163 }
164
165 if (maybe_current_function) |current_function| {
166 log.err("OpFunction {f} does not have an OpFunctionEnd", .{current_function});
167 return error.InvalidPhysicalFormat;
168 }
169
170 return .{
171 .functions = functions.unmanaged,
172 .callee_store = callee_store.items,
173 .result_id_to_code_offset = result_id_to_code_offset.unmanaged,
174 };
175 }
176};
177
178const AliveMarker = struct {
179 parser: *BinaryModule.Parser,
180 binary: BinaryModule,
181 info: ModuleInfo,
182 result_id_offsets: std.ArrayList(u16),
183 alive: std.DynamicBitSetUnmanaged,
184
185 fn markAlive(self: *AliveMarker, result_id: ResultId) BinaryModule.ParseError!void {
186 const index = self.info.result_id_to_code_offset.getIndex(result_id) orelse {
187 log.err("undefined result-id {f}", .{result_id});
188 return error.InvalidId;
189 };
190
191 if (self.alive.isSet(index)) {
192 return;
193 }
194 self.alive.set(index);
195
196 const offset = self.info.result_id_to_code_offset.values()[index];
197 const inst = self.binary.instructionAt(offset);
198
199 if (inst.opcode == .OpFunction) {
200 try self.markFunctionAlive(inst);
201 } else {
202 try self.markInstructionAlive(inst);
203 }
204 }
205
206 fn markFunctionAlive(
207 self: *AliveMarker,
208 func_inst: BinaryModule.Instruction,
209 ) !void {
210 // Go through the instruction and mark the
211 // operands of each instruction alive.
212 var it = self.binary.iterateInstructionsFrom(func_inst.offset);
213 try self.markInstructionAlive(it.next().?);
214 while (it.next()) |inst| {
215 if (inst.opcode == .OpFunctionEnd) {
216 break;
217 }
218
219 if (!canPrune(inst.opcode)) {
220 try self.markInstructionAlive(inst);
221 }
222 }
223 }
224
225 fn markInstructionAlive(
226 self: *AliveMarker,
227 inst: BinaryModule.Instruction,
228 ) !void {
229 const start_offset = self.result_id_offsets.items.len;
230 try self.parser.parseInstructionResultIds(self.binary, inst, &self.result_id_offsets);
231 const end_offset = self.result_id_offsets.items.len;
232
233 // Recursive calls to markInstructionAlive() might change the pointer in self.result_id_offsets,
234 // so we need to iterate it manually.
235 var i = start_offset;
236 while (i < end_offset) : (i += 1) {
237 const offset = self.result_id_offsets.items[i];
238 try self.markAlive(@enumFromInt(inst.operands[offset]));
239 }
240 }
241};
242
243fn removeIdsFromMap(a: Allocator, map: anytype, info: ModuleInfo, alive_marker: AliveMarker) !void {
244 var to_remove = std.ArrayList(ResultId).init(a);
245 var it = map.iterator();
246 while (it.next()) |entry| {
247 const id = entry.key_ptr.*;
248 const index = info.result_id_to_code_offset.getIndex(id).?;
249 if (!alive_marker.alive.isSet(index)) {
250 try to_remove.append(id);
251 }
252 }
253
254 for (to_remove.items) |id| {
255 assert(map.remove(id));
256 }
257}
258
259pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Progress.Node) !void {
260 const sub_node = progress.start("Prune unused IDs", 0);
261 defer sub_node.end();
262
263 var arena = std.heap.ArenaAllocator.init(parser.a);
264 defer arena.deinit();
265 const a = arena.allocator();
266
267 const info = try ModuleInfo.parse(a, parser, binary.*);
268
269 var alive_marker = AliveMarker{
270 .parser = parser,
271 .binary = binary.*,
272 .info = info,
273 .result_id_offsets = std.ArrayList(u16).init(a),
274 .alive = try std.DynamicBitSetUnmanaged.initEmpty(a, info.result_id_to_code_offset.count()),
275 };
276
277 // Mark initial stuff as alive
278 {
279 var it = binary.iterateInstructions();
280 while (it.next()) |inst| {
281 if (inst.opcode == .OpFunction) {
282 // No need to process further.
283 break;
284 } else if (!canPrune(inst.opcode)) {
285 try alive_marker.markInstructionAlive(inst);
286 }
287 }
288 }
289
290 var section = Section{};
291
292 sub_node.setEstimatedTotalItems(binary.instructions.len);
293
294 var new_functions_section: ?usize = null;
295 var it = binary.iterateInstructions();
296 skip: while (it.next()) |inst| {
297 defer sub_node.setCompletedItems(inst.offset);
298
299 const inst_spec = parser.getInstSpec(inst.opcode).?;
300
301 reemit: {
302 if (!canPrune(inst.opcode)) {
303 break :reemit;
304 }
305
306 // Result-id can only be the first or second operand
307 const result_id: ResultId = for (0..2) |i| {
308 if (inst_spec.operands.len > i and inst_spec.operands[i].kind == .id_result) {
309 break @enumFromInt(inst.operands[i]);
310 }
311 } else {
312 // Instruction can be pruned but doesn't have a result id.
313 // Check all operands to see if they are alive, and emit it only if so.
314 alive_marker.result_id_offsets.items.len = 0;
315 try parser.parseInstructionResultIds(binary.*, inst, &alive_marker.result_id_offsets);
316 for (alive_marker.result_id_offsets.items) |offset| {
317 const id: ResultId = @enumFromInt(inst.operands[offset]);
318 const index = info.result_id_to_code_offset.getIndex(id).?;
319
320 if (!alive_marker.alive.isSet(index)) {
321 continue :skip;
322 }
323 }
324
325 break :reemit;
326 };
327
328 const index = info.result_id_to_code_offset.getIndex(result_id).?;
329 if (alive_marker.alive.isSet(index)) {
330 break :reemit;
331 }
332
333 if (inst.opcode != .OpFunction) {
334 // Instruction can be pruned and its not alive, so skip it.
335 continue :skip;
336 }
337
338 // We're at the start of a function that can be pruned, so skip everything until
339 // we encounter an OpFunctionEnd.
340 while (it.next()) |body_inst| {
341 if (body_inst.opcode == .OpFunctionEnd)
342 break;
343 }
344
345 continue :skip;
346 }
347
348 if (inst.opcode == .OpFunction and new_functions_section == null) {
349 new_functions_section = section.instructions.items.len;
350 }
351
352 try section.emitRawInstruction(a, inst.opcode, inst.operands);
353 }
354
355 // This pass might have pruned ext inst imports or arith types, update
356 // those maps to main consistency.
357 try removeIdsFromMap(a, &binary.ext_inst_map, info, alive_marker);
358 try removeIdsFromMap(a, &binary.arith_type_width, info, alive_marker);
359
360 binary.instructions = try parser.a.dupe(Word, section.toWords());
361 binary.sections.functions = new_functions_section orelse binary.instructions.len;
362}