| author | |
| committer | |
| log | 6bb6013d3a4da18ca79c5d4d882920cc147520bf |
| tree | 8ddc329028ab11506fc22fd9a9a673243e26628a |
| parent | 298328581534de4b7cb984f7d5e8a5c4080a881c |
| parent | 64563e2fffd0e304019c343a32f31c925be20ea2 |
| signature |
spirv: remove prune_unused ISel15 files changed, 154 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 | 274 | .storage_class = storage_class, |
| 275 | 275 | }); |
| 276 | 276 | |
| 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 | 284 | switch (target.os.tag) { |
| 278 | 285 | .vulkan, .opengl => { |
| 279 | 286 | if (ty.zigTypeTag(zcu) == .@"struct") { |
| ... | ... | @@ -348,7 +355,7 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { |
| 348 | 355 | .id_result_type = ptr_ty_id, |
| 349 | 356 | .id_result = result_id, |
| 350 | 357 | .set = try cg.module.importInstructionSet(.zig), |
| 351 | .instruction = .{ .inst = 0 }, // TODO: Put this definition somewhere... | |
| 358 | .instruction = .{ .inst = @intFromEnum(spec.Zig.InvocationGlobal) }, | |
| 352 | 359 | .id_ref_4 = &.{initializer_id}, |
| 353 | 360 | }); |
| 354 | 361 | } else { |
| ... | ... | @@ -356,7 +363,7 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { |
| 356 | 363 | .id_result_type = ptr_ty_id, |
| 357 | 364 | .id_result = result_id, |
| 358 | 365 | .set = try cg.module.importInstructionSet(.zig), |
| 359 | .instruction = .{ .inst = 0 }, // TODO: Put this definition somewhere... | |
| 366 | .instruction = .{ .inst = @intFromEnum(spec.Zig.InvocationGlobal) }, | |
| 360 | 367 | .id_ref_4 = &.{}, |
| 361 | 368 | }); |
| 362 | 369 | } |
| ... | ... | @@ -498,7 +505,7 @@ fn resolveUav(cg: *CodeGen, val: InternPool.Index) !Id { |
| 498 | 505 | .id_result_type = fn_decl_ptr_ty_id, |
| 499 | 506 | .id_result = result_id, |
| 500 | 507 | .set = try cg.module.importInstructionSet(.zig), |
| 501 | .instruction = .{ .inst = 0 }, // TODO: Put this definition somewhere... | |
| 508 | .instruction = .{ .inst = @intFromEnum(spec.Zig.InvocationGlobal) }, | |
| 502 | 509 | .id_ref_4 = &.{initializer_id}, |
| 503 | 510 | }); |
| 504 | 511 | } |
| ... | ... | @@ -1037,9 +1044,18 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { |
| 1037 | 1044 | const gpa = cg.module.gpa; |
| 1038 | 1045 | const pt = cg.pt; |
| 1039 | 1046 | const zcu = cg.module.zcu; |
| 1047 | const target = zcu.getTarget(); | |
| 1040 | 1048 | switch (derivation) { |
| 1041 | 1049 | .comptime_alloc_ptr, .comptime_field_ptr => unreachable, |
| 1042 | 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 | 1059 | const result_ty_id = try cg.resolveType(int.ptr_ty, .direct); |
| 1044 | 1060 | // TODO: This can probably be an OpSpecConstantOp Bitcast, but |
| 1045 | 1061 | // that is not implemented by Mesa yet. Therefore, just generate it |
| ... | ... | @@ -1137,7 +1153,7 @@ fn constantUavRef( |
| 1137 | 1153 | // Uav refs are always generic. |
| 1138 | 1154 | assert(ty.ptrAddressSpace(zcu) == .generic); |
| 1139 | 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 | 1157 | const ptr_id = try cg.resolveUav(uav.val); |
| 1142 | 1158 | |
| 1143 | 1159 | if (decl_ptr_ty_id != ty_id) { |
| ... | ... | @@ -1327,7 +1343,10 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { |
| 1327 | 1343 | }, |
| 1328 | 1344 | .void => switch (repr) { |
| 1329 | 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 | 1351 | .bool => switch (repr) { |
| 1333 | 1352 | .direct => return try cg.module.boolType(), |
| ... | ... | @@ -1337,6 +1356,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { |
| 1337 | 1356 | const int_info = ty.intInfo(zcu); |
| 1338 | 1357 | if (int_info.bits == 0) { |
| 1339 | 1358 | assert(repr == .indirect); |
| 1359 | if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); | |
| 1340 | 1360 | return try cg.module.opaqueType("u0"); |
| 1341 | 1361 | } |
| 1342 | 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 | 1389 | |
| 1370 | 1390 | if (!elem_ty.hasRuntimeBitsIgnoreComptime(zcu)) { |
| 1371 | 1391 | assert(repr == .indirect); |
| 1392 | if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); | |
| 1372 | 1393 | return try cg.module.opaqueType("zero-sized-array"); |
| 1373 | 1394 | } else if (total_len == 0) { |
| 1374 | 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 | 1611 | return try cg.module.structType(&member_types, &member_names, null, .none); |
| 1591 | 1612 | }, |
| 1592 | 1613 | .@"opaque" => { |
| 1614 | if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); | |
| 1593 | 1615 | const type_name = try cg.resolveTypeName(ty); |
| 1594 | 1616 | defer gpa.free(type_name); |
| 1595 | 1617 | return try cg.module.opaqueType(type_name); |
| ... | ... | @@ -2510,11 +2532,7 @@ fn generateTestEntryPoint( |
| 2510 | 2532 | try cg.module.declareEntryPoint(spv_decl_index, test_name, execution_mode, null); |
| 2511 | 2533 | } |
| 2512 | 2534 | |
| 2513 | fn intFromBool(cg: *CodeGen, value: Temporary) !Temporary { | |
| 2514 | return try cg.intFromBool2(value, Type.u1); | |
| 2515 | } | |
| 2516 | ||
| 2517 | fn intFromBool2(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary { | |
| 2535 | fn intFromBool(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary { | |
| 2518 | 2536 | const zero_id = try cg.constInt(result_ty, 0); |
| 2519 | 2537 | const one_id = try cg.constInt(result_ty, 1); |
| 2520 | 2538 | |
| ... | ... | @@ -2558,7 +2576,7 @@ fn convertToIndirect(cg: *CodeGen, ty: Type, operand_id: Id) !Id { |
| 2558 | 2576 | const zcu = cg.module.zcu; |
| 2559 | 2577 | switch (ty.scalarType(zcu).zigTypeTag(zcu)) { |
| 2560 | 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 | 2580 | return try result.materialize(cg); |
| 2563 | 2581 | }, |
| 2564 | 2582 | else => return operand_id, |
| ... | ... | @@ -2958,7 +2976,7 @@ fn normalize(cg: *CodeGen, value: Temporary, info: ArithmeticTypeInfo) !Temporar |
| 2958 | 2976 | .composite_integer, .integer, .bool, .float => return value, |
| 2959 | 2977 | .strange_integer => switch (info.signedness) { |
| 2960 | 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 | 2980 | const mask_id = try cg.constInt(ty.scalarType(zcu), mask_value); |
| 2963 | 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 | 3015 | |
| 2998 | 3016 | const div = try cg.buildBinary(.OpSDiv, lhs, rhs); |
| 2999 | 3017 | const rem = try cg.buildBinary(.OpSRem, lhs, rhs); |
| 3000 | ||
| 3001 | 3018 | const zero: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, 0)); |
| 3002 | ||
| 3003 | const rem_is_not_zero = try cg.buildCmp(.OpINotEqual, rem, zero); | |
| 3004 | ||
| 3005 | const result_negative = try cg.buildCmp( | |
| 3006 | .OpLogicalNotEqual, | |
| 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 | ||
| 3019 | const rem_non_zero = try cg.buildCmp(.OpINotEqual, rem, zero); | |
| 3020 | const lhs_rhs_xor = try cg.buildBinary(.OpBitwiseXor, lhs, rhs); | |
| 3021 | const signs_differ = try cg.buildCmp(.OpSLessThan, lhs_rhs_xor, zero); | |
| 3022 | const adjust = try cg.buildBinary(.OpLogicalAnd, rem_non_zero, signs_differ); | |
| 3023 | const result = try cg.buildBinary(.OpISub, div, try cg.intFromBool(adjust, div.ty)); | |
| 3022 | 3024 | return try result.materialize(cg); |
| 3023 | 3025 | }, |
| 3024 | 3026 | .float => { |
| ... | ... | @@ -3032,10 +3034,8 @@ fn airDivFloor(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 3032 | 3034 | |
| 3033 | 3035 | fn airDivTrunc(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 3034 | 3036 | const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 3035 | ||
| 3036 | 3037 | const lhs = try cg.temporary(bin_op.lhs); |
| 3037 | 3038 | const rhs = try cg.temporary(bin_op.rhs); |
| 3038 | ||
| 3039 | 3039 | const info = cg.arithmeticTypeInfo(lhs.ty); |
| 3040 | 3040 | switch (info.class) { |
| 3041 | 3041 | .composite_integer => unreachable, // TODO |
| ... | ... | @@ -3073,12 +3073,9 @@ fn airArithOp( |
| 3073 | 3073 | comptime uop: Opcode, |
| 3074 | 3074 | ) !?Id { |
| 3075 | 3075 | const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 3076 | ||
| 3077 | 3076 | const lhs = try cg.temporary(bin_op.lhs); |
| 3078 | 3077 | const rhs = try cg.temporary(bin_op.rhs); |
| 3079 | ||
| 3080 | 3078 | const info = cg.arithmeticTypeInfo(lhs.ty); |
| 3081 | ||
| 3082 | 3079 | const result = switch (info.class) { |
| 3083 | 3080 | .composite_integer => unreachable, // TODO |
| 3084 | 3081 | .integer, .strange_integer => switch (info.signedness) { |
| ... | ... | @@ -3088,7 +3085,6 @@ fn airArithOp( |
| 3088 | 3085 | .float => try cg.buildBinary(fop, lhs, rhs), |
| 3089 | 3086 | .bool => unreachable, |
| 3090 | 3087 | }; |
| 3091 | ||
| 3092 | 3088 | return try result.materialize(cg); |
| 3093 | 3089 | } |
| 3094 | 3090 | |
| ... | ... | @@ -3105,12 +3101,10 @@ fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary { |
| 3105 | 3101 | const zcu = cg.module.zcu; |
| 3106 | 3102 | const target = cg.module.zcu.getTarget(); |
| 3107 | 3103 | const operand_info = cg.arithmeticTypeInfo(value.ty); |
| 3108 | ||
| 3109 | 3104 | switch (operand_info.class) { |
| 3110 | 3105 | .float => return try cg.buildUnary(.f_abs, value), |
| 3111 | 3106 | .integer, .strange_integer => { |
| 3112 | 3107 | const abs_value = try cg.buildUnary(.i_abs, value); |
| 3113 | ||
| 3114 | 3108 | switch (target.os.tag) { |
| 3115 | 3109 | .vulkan, .opengl => { |
| 3116 | 3110 | if (value.ty.intInfo(zcu).signedness == .signed) { |
| ... | ... | @@ -3119,7 +3113,6 @@ fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary { |
| 3119 | 3113 | }, |
| 3120 | 3114 | else => {}, |
| 3121 | 3115 | } |
| 3122 | ||
| 3123 | 3116 | return try cg.normalize(abs_value, cg.arithmeticTypeInfo(result_ty)); |
| 3124 | 3117 | }, |
| 3125 | 3118 | .composite_integer => unreachable, // TODO |
| ... | ... | @@ -3134,19 +3127,18 @@ fn airAddSubOverflow( |
| 3134 | 3127 | u_opcode: Opcode, |
| 3135 | 3128 | s_opcode: Opcode, |
| 3136 | 3129 | ) !?Id { |
| 3137 | _ = s_opcode; | |
| 3138 | 3130 | // Note: OpIAddCarry and OpISubBorrow are not really useful here: For unsigned numbers, |
| 3139 | 3131 | // there is in both cases only one extra operation required. For signed operations, |
| 3140 | 3132 | // the overflow bit is set then going from 0x80.. to 0x00.., but this doesn't actually |
| 3141 | 3133 | // normally set a carry bit. So the SPIR-V overflow operations are not particularly |
| 3142 | 3134 | // useful here. |
| 3143 | 3135 | |
| 3136 | _ = s_opcode; | |
| 3137 | ||
| 3144 | 3138 | const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 3145 | 3139 | const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data; |
| 3146 | ||
| 3147 | 3140 | const lhs = try cg.temporary(extra.lhs); |
| 3148 | 3141 | const rhs = try cg.temporary(extra.rhs); |
| 3149 | ||
| 3150 | 3142 | const result_ty = cg.typeOfIndex(inst); |
| 3151 | 3143 | |
| 3152 | 3144 | const info = cg.arithmeticTypeInfo(lhs.ty); |
| ... | ... | @@ -3158,7 +3150,6 @@ fn airAddSubOverflow( |
| 3158 | 3150 | |
| 3159 | 3151 | const sum = try cg.buildBinary(add, lhs, rhs); |
| 3160 | 3152 | const result = try cg.normalize(sum, info); |
| 3161 | ||
| 3162 | 3153 | const overflowed = switch (info.signedness) { |
| 3163 | 3154 | // Overflow happened if the result is smaller than either of the operands. It doesn't matter which. |
| 3164 | 3155 | // For subtraction the conditions need to be swapped. |
| ... | ... | @@ -3173,38 +3164,31 @@ fn airAddSubOverflow( |
| 3173 | 3164 | // and the result's sign is different from the minuend's (a's) sign. |
| 3174 | 3165 | // (sign(a) != sign(b)) && (sign(a) != sign(result)) |
| 3175 | 3166 | const zero: Temporary = .init(rhs.ty, try cg.constInt(rhs.ty, 0)); |
| 3176 | ||
| 3177 | 3167 | const lhs_is_neg = try cg.buildCmp(.OpSLessThan, lhs, zero); |
| 3178 | 3168 | const rhs_is_neg = try cg.buildCmp(.OpSLessThan, rhs, zero); |
| 3179 | 3169 | const result_is_neg = try cg.buildCmp(.OpSLessThan, result, zero); |
| 3180 | ||
| 3181 | 3170 | const signs_match = try cg.buildCmp(.OpLogicalEqual, lhs_is_neg, rhs_is_neg); |
| 3182 | 3171 | const result_sign_differs = try cg.buildCmp(.OpLogicalNotEqual, lhs_is_neg, result_is_neg); |
| 3183 | ||
| 3184 | const overflow_condition = if (add == .OpIAdd) | |
| 3185 | signs_match | |
| 3186 | else // .OpISub | |
| 3187 | try cg.buildUnary(.l_not, signs_match); | |
| 3188 | ||
| 3172 | const overflow_condition = switch (add) { | |
| 3173 | .OpIAdd => signs_match, | |
| 3174 | .OpISub => try cg.buildUnary(.l_not, signs_match), | |
| 3175 | else => unreachable, | |
| 3176 | }; | |
| 3189 | 3177 | break :blk try cg.buildCmp(.OpLogicalAnd, overflow_condition, result_sign_differs); |
| 3190 | 3178 | }, |
| 3191 | 3179 | }; |
| 3192 | 3180 | |
| 3193 | const ov = try cg.intFromBool(overflowed); | |
| 3194 | ||
| 3181 | const ov = try cg.intFromBool(overflowed, .u1); | |
| 3195 | 3182 | const result_ty_id = try cg.resolveType(result_ty, .direct); |
| 3196 | 3183 | return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) }); |
| 3197 | 3184 | } |
| 3198 | 3185 | |
| 3199 | 3186 | fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 3200 | 3187 | const pt = cg.pt; |
| 3201 | ||
| 3202 | 3188 | const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 3203 | 3189 | const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data; |
| 3204 | ||
| 3205 | 3190 | const lhs = try cg.temporary(extra.lhs); |
| 3206 | 3191 | const rhs = try cg.temporary(extra.rhs); |
| 3207 | ||
| 3208 | 3192 | const result_ty = cg.typeOfIndex(inst); |
| 3209 | 3193 | |
| 3210 | 3194 | const info = cg.arithmeticTypeInfo(lhs.ty); |
| ... | ... | @@ -3237,20 +3221,15 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 3237 | 3221 | const op_ty = try pt.intType(.unsigned, op_ty_bits); |
| 3238 | 3222 | const casted_lhs = try cg.buildConvert(op_ty, lhs); |
| 3239 | 3223 | const casted_rhs = try cg.buildConvert(op_ty, rhs); |
| 3240 | ||
| 3241 | 3224 | const full_result = try cg.buildBinary(.OpIMul, casted_lhs, casted_rhs); |
| 3242 | ||
| 3243 | 3225 | const low_bits = try cg.buildConvert(lhs.ty, full_result); |
| 3244 | 3226 | const result = try cg.normalize(low_bits, info); |
| 3245 | ||
| 3246 | 3227 | // Shift the result bits away to get the overflow bits. |
| 3247 | 3228 | const shift: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, info.bits)); |
| 3248 | 3229 | const overflow = try cg.buildBinary(.OpShiftRightLogical, full_result, shift); |
| 3249 | ||
| 3250 | 3230 | // Directly check if its zero in the op_ty without converting first. |
| 3251 | 3231 | const zero: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, 0)); |
| 3252 | 3232 | const overflowed = try cg.buildCmp(.OpINotEqual, zero, overflow); |
| 3253 | ||
| 3254 | 3233 | break :blk .{ result, overflowed }; |
| 3255 | 3234 | } |
| 3256 | 3235 | |
| ... | ... | @@ -3362,7 +3341,7 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 3362 | 3341 | }, |
| 3363 | 3342 | }; |
| 3364 | 3343 | |
| 3365 | const ov = try cg.intFromBool(overflowed); | |
| 3344 | const ov = try cg.intFromBool(overflowed, .u1); | |
| 3366 | 3345 | |
| 3367 | 3346 | const result_ty_id = try cg.resolveType(result_ty, .direct); |
| 3368 | 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 | 3382 | }; |
| 3404 | 3383 | |
| 3405 | 3384 | const overflowed = try cg.buildCmp(.OpINotEqual, base, right); |
| 3406 | const ov = try cg.intFromBool(overflowed); | |
| 3385 | const ov = try cg.intFromBool(overflowed, .u1); | |
| 3407 | 3386 | |
| 3408 | 3387 | const result_ty_id = try cg.resolveType(result_ty, .direct); |
| 3409 | 3388 | return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) }); |
| ... | ... | @@ -3931,6 +3910,7 @@ fn bitCast( |
| 3931 | 3910 | ) !Id { |
| 3932 | 3911 | const gpa = cg.module.gpa; |
| 3933 | 3912 | const zcu = cg.module.zcu; |
| 3913 | const target = zcu.getTarget(); | |
| 3934 | 3914 | const src_ty_id = try cg.resolveType(src_ty, .direct); |
| 3935 | 3915 | const dst_ty_id = try cg.resolveType(dst_ty, .direct); |
| 3936 | 3916 | |
| ... | ... | @@ -3941,6 +3921,15 @@ fn bitCast( |
| 3941 | 3921 | // See fn bitCast in llvm.zig |
| 3942 | 3922 | |
| 3943 | 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 | 3933 | const result_id = cg.module.allocId(); |
| 3945 | 3934 | try cg.body.emit(gpa, .OpConvertUToPtr, .{ |
| 3946 | 3935 | .id_result_type = dst_ty_id, |
| ... | ... | @@ -3967,7 +3956,8 @@ fn bitCast( |
| 3967 | 3956 | |
| 3968 | 3957 | const dst_ptr_ty_id = try cg.module.ptrType(dst_ty_id, .function); |
| 3969 | 3958 | |
| 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 | 3961 | try cg.store(src_ty, tmp_id, src_id, .{}); |
| 3972 | 3962 | const casted_ptr_id = cg.module.allocId(); |
| 3973 | 3963 | try cg.body.emit(gpa, .OpBitcast, .{ |
| ... | ... | @@ -3997,7 +3987,7 @@ fn airBitCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 3997 | 3987 | const result_ty = cg.typeOfIndex(inst); |
| 3998 | 3988 | if (operand_ty.toIntern() == .bool_type) { |
| 3999 | 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 | 3991 | return try result.materialize(cg); |
| 4002 | 3992 | } |
| 4003 | 3993 | const operand_id = try cg.resolve(ty_op.operand); |
| ... | ... | @@ -4420,7 +4410,6 @@ fn airArrayElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 4420 | 4410 | // TODO: This backend probably also should use isByRef from llvm... |
| 4421 | 4411 | |
| 4422 | 4412 | const is_vector = array_ty.isVector(zcu); |
| 4423 | ||
| 4424 | 4413 | const elem_repr: Repr = if (is_vector) .direct else .indirect; |
| 4425 | 4414 | const array_ty_id = try cg.resolveType(array_ty, .direct); |
| 4426 | 4415 | const elem_ty_id = try cg.resolveType(elem_ty, elem_repr); |
| ... | ... | @@ -4588,7 +4577,8 @@ fn unionInit( |
| 4588 | 4577 | return try cg.constInt(tag_ty, tag_int); |
| 4589 | 4578 | } |
| 4590 | 4579 | |
| 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); | |
| 4592 | 4582 | |
| 4593 | 4583 | if (layout.tag_size != 0) { |
| 4594 | 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 | 4699 | const layout = cg.unionLayout(object_ty); |
| 4710 | 4700 | assert(layout.has_payload); |
| 4711 | 4701 | |
| 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 | 4704 | try cg.store(object_ty, tmp_id, object_id, .{}); |
| 4714 | 4705 | |
| 4715 | 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 | 4724 | |
| 4734 | 4725 | fn airFieldParentPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 4735 | 4726 | const zcu = cg.module.zcu; |
| 4727 | const target = zcu.getTarget(); | |
| 4736 | 4728 | const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 4737 | 4729 | const extra = cg.air.extraData(Air.FieldParentPtr, ty_pl.payload).data; |
| 4738 | 4730 | |
| 4739 | const parent_ty = ty_pl.ty.toType().childType(zcu); | |
| 4740 | const result_ty_id = try cg.resolveType(ty_pl.ty.toType(), .indirect); | |
| 4731 | const parent_ptr_ty = ty_pl.ty.toType(); | |
| 4732 | const parent_ty = parent_ptr_ty.childType(zcu); | |
| 4733 | const result_ty_id = try cg.resolveType(parent_ptr_ty, .indirect); | |
| 4741 | 4734 | |
| 4742 | 4735 | const field_ptr = try cg.resolve(extra.field_ptr); |
| 4736 | const field_ptr_ty = cg.typeOf(extra.field_ptr); | |
| 4743 | 4737 | const field_ptr_int = try cg.intFromPtr(field_ptr); |
| 4744 | 4738 | const field_offset = parent_ty.structFieldOffset(extra.field_index, zcu); |
| 4745 | 4739 | |
| ... | ... | @@ -4753,6 +4747,15 @@ fn airFieldParentPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 4753 | 4747 | break :base_ptr_int try result.materialize(cg); |
| 4754 | 4748 | }; |
| 4755 | 4749 | |
| 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 | 4759 | const base_ptr = cg.module.allocId(); |
| 4757 | 4760 | try cg.body.emit(cg.module.gpa, .OpConvertUToPtr, .{ |
| 4758 | 4761 | .id_result_type = result_ty_id, |
| ... | ... | @@ -4821,46 +4824,33 @@ fn airStructFieldPtrIndex(cg: *CodeGen, inst: Air.Inst.Index, field_index: u32) |
| 4821 | 4824 | return try cg.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index); |
| 4822 | 4825 | } |
| 4823 | 4826 | |
| 4824 | const AllocOptions = struct { | |
| 4825 | initializer: ?Id = null, | |
| 4826 | /// The final storage class of the pointer. This may be either `.Generic` or `.Function`. | |
| 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. | |
| 4836 | fn 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(); | |
| 4827 | fn alloc(cg: *CodeGen, ty_id: Id, initializer: ?Id) !Id { | |
| 4828 | const ptr_ty_id = try cg.module.ptrType(ty_id, .function); | |
| 4829 | const result_id = cg.module.allocId(); | |
| 4847 | 4830 | try cg.prologue.emit(cg.module.gpa, .OpVariable, .{ |
| 4848 | .id_result_type = ptr_fn_ty_id, | |
| 4849 | .id_result = var_id, | |
| 4831 | .id_result_type = ptr_ty_id, | |
| 4832 | .id_result = result_id, | |
| 4850 | 4833 | .storage_class = .function, |
| 4851 | .initializer = options.initializer, | |
| 4834 | .initializer = initializer, | |
| 4852 | 4835 | }); |
| 4853 | ||
| 4854 | return var_id; | |
| 4836 | return result_id; | |
| 4855 | 4837 | } |
| 4856 | 4838 | |
| 4857 | 4839 | fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 4858 | 4840 | const zcu = cg.module.zcu; |
| 4841 | const target = zcu.getTarget(); | |
| 4859 | 4842 | const ptr_ty = cg.typeOfIndex(inst); |
| 4860 | 4843 | const child_ty = ptr_ty.childType(zcu); |
| 4861 | return try cg.alloc(child_ty, .{ | |
| 4862 | .storage_class = cg.module.storageClass(ptr_ty.ptrAddressSpace(zcu)), | |
| 4863 | }); | |
| 4844 | const child_ty_id = try cg.resolveType(child_ty, .indirect); | |
| 4845 | const ptr_align = ptr_ty.ptrAlignment(zcu); | |
| 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 | } |
| 4865 | 4855 | |
| 4866 | 4856 | fn airArg(cg: *CodeGen) Id { |
| ... | ... | @@ -5087,7 +5077,8 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, body: []const Air.Inst.Index) |
| 5087 | 5077 | }; |
| 5088 | 5078 | |
| 5089 | 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 | 5082 | try cf.block_results.putNoClobber(gpa, inst, block_result_var_id); |
| 5092 | 5083 | break :blk block_result_var_id; |
| 5093 | 5084 | } else null; |
src/codegen/spirv/Module.zig+6-5| ... | ... | @@ -676,8 +676,9 @@ pub fn structType( |
| 676 | 676 | ip_index: InternPool.Index, |
| 677 | 677 | ) !Id { |
| 678 | 678 | const target = module.zcu.getTarget(); |
| 679 | const actual_ip_index = if (module.zcu.comp.config.root_strip) .none else ip_index; | |
| 679 | 680 | |
| 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 | 682 | const result_id = module.allocId(); |
| 682 | 683 | const types_dup = try module.arena.dupe(Id, types); |
| 683 | 684 | try module.sections.globals.emit(module.gpa, .OpTypeStruct, .{ |
| ... | ... | @@ -710,10 +711,7 @@ pub fn structType( |
| 710 | 711 | |
| 711 | 712 | try module.cache.struct_types.put( |
| 712 | 713 | module.gpa, |
| 713 | .{ | |
| 714 | .fields = types_dup, | |
| 715 | .ip_index = if (module.zcu.comp.config.root_strip) .none else ip_index, | |
| 716 | }, | |
| 714 | .{ .fields = types_dup, .ip_index = actual_ip_index }, | |
| 717 | 715 | result_id, |
| 718 | 716 | ); |
| 719 | 717 | return result_id; |
| ... | ... | @@ -874,6 +872,7 @@ pub fn declareEntryPoint( |
| 874 | 872 | } |
| 875 | 873 | |
| 876 | 874 | pub fn debugName(module: *Module, target: Id, name: []const u8) !void { |
| 875 | if (module.zcu.comp.config.root_strip) return; | |
| 877 | 876 | try module.sections.debug_names.emit(module.gpa, .OpName, .{ |
| 878 | 877 | .target = target, |
| 879 | 878 | .name = name, |
| ... | ... | @@ -881,12 +880,14 @@ pub fn debugName(module: *Module, target: Id, name: []const u8) !void { |
| 881 | 880 | } |
| 882 | 881 | |
| 883 | 882 | pub fn debugNameFmt(module: *Module, target: Id, comptime fmt: []const u8, args: anytype) !void { |
| 883 | if (module.zcu.comp.config.root_strip) return; | |
| 884 | 884 | const name = try std.fmt.allocPrint(module.gpa, fmt, args); |
| 885 | 885 | defer module.gpa.free(name); |
| 886 | 886 | try module.debugName(target, name); |
| 887 | 887 | } |
| 888 | 888 | |
| 889 | 889 | pub fn memberDebugName(module: *Module, target: Id, member: u32, name: []const u8) !void { |
| 890 | if (module.zcu.comp.config.root_strip) return; | |
| 890 | 891 | try module.sections.debug_names.emit(module.gpa, .OpMemberName, .{ |
| 891 | 892 | .type = target, |
| 892 | 893 | .member = member, |
src/link/SpirV.zig+4-11| ... | ... | @@ -10,10 +10,11 @@ const Compilation = @import("../Compilation.zig"); |
| 10 | 10 | const link = @import("../link.zig"); |
| 11 | 11 | const Air = @import("../Air.zig"); |
| 12 | 12 | const Type = @import("../Type.zig"); |
| 13 | const BinaryModule = @import("SpirV/BinaryModule.zig"); | |
| 14 | 13 | const CodeGen = @import("../codegen/spirv/CodeGen.zig"); |
| 15 | 14 | const Module = @import("../codegen/spirv/Module.zig"); |
| 16 | 15 | const trace = @import("../tracy.zig").trace; |
| 16 | const BinaryModule = @import("SpirV/BinaryModule.zig"); | |
| 17 | const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig"); | |
| 17 | 18 | |
| 18 | 19 | const spec = @import("../codegen/spirv/spec.zig"); |
| 19 | 20 | const Id = spec.Id; |
| ... | ... | @@ -279,7 +280,7 @@ pub fn flush( |
| 279 | 280 | const module = try linker.module.finalize(arena); |
| 280 | 281 | errdefer arena.free(module); |
| 281 | 282 | |
| 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 | 284 | error.OutOfMemory => return error.OutOfMemory, |
| 284 | 285 | else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}), |
| 285 | 286 | }; |
| ... | ... | @@ -288,18 +289,10 @@ pub fn flush( |
| 288 | 289 | return diags.fail("failed to write: {s}", .{@errorName(err)}); |
| 289 | 290 | } |
| 290 | 291 | |
| 291 | fn linkModule(linker: *Linker, 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 | ||
| 292 | fn linkModule(arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word { | |
| 297 | 293 | var parser = try BinaryModule.Parser.init(arena); |
| 298 | 294 | defer parser.deinit(); |
| 299 | 295 | var binary = try parser.parse(module); |
| 300 | ||
| 301 | 296 | try lower_invocation_globals.run(&parser, &binary, progress); |
| 302 | try prune_unused.run(&parser, &binary, progress); | |
| 303 | ||
| 304 | 297 | return binary.finalize(arena); |
| 305 | 298 | } |
src/link/SpirV/lower_invocation_globals.zig+10-1| ... | ... | @@ -382,6 +382,15 @@ const ModuleBuilder = struct { |
| 382 | 382 | var it = binary.iterateInstructions(); |
| 383 | 383 | while (it.next()) |inst| { |
| 384 | 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 | 394 | .OpExtInst => { |
| 386 | 395 | const set_id: ResultId = @enumFromInt(inst.operands[2]); |
| 387 | 396 | const set_inst = inst.operands[3]; |
| ... | ... | @@ -482,7 +491,7 @@ const ModuleBuilder = struct { |
| 482 | 491 | return entry.value_ptr.*; |
| 483 | 492 | } |
| 484 | 493 | |
| 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 | 495 | fn rewriteFunctions( |
| 487 | 496 | self: *ModuleBuilder, |
| 488 | 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 | ||
| 9 | const std = @import("std"); | |
| 10 | const Allocator = std.mem.Allocator; | |
| 11 | const assert = std.debug.assert; | |
| 12 | const log = std.log.scoped(.spirv_link); | |
| 13 | ||
| 14 | const BinaryModule = @import("BinaryModule.zig"); | |
| 15 | const Section = @import("../../codegen/spirv/Section.zig"); | |
| 16 | const spec = @import("../../codegen/spirv/spec.zig"); | |
| 17 | const Opcode = spec.Opcode; | |
| 18 | const ResultId = spec.Id; | |
| 19 | const 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. | |
| 28 | fn 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 | ||
| 69 | const 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 | ||
| 178 | const 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 | ||
| 243 | fn 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 | ||
| 259 | pub 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 | } |
test/behavior/align.zig+2| ... | ... | @@ -561,6 +561,8 @@ test "function pointer @intFromPtr/@ptrFromInt roundtrip" { |
| 561 | 561 | } |
| 562 | 562 | |
| 563 | 563 | test "function pointer align mask" { |
| 564 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 565 | ||
| 564 | 566 | const int = if (builtin.cpu.arch.isArm() or builtin.cpu.arch.isMIPS()) 0x20202021 else 0x20202020; |
| 565 | 567 | const unaligned: *const fn () callconv(.c) void = @ptrFromInt(int); |
| 566 | 568 | const aligned: *align(16) const fn () callconv(.c) void = @alignCast(unaligned); |
test/behavior/array.zig+2| ... | ... | @@ -1088,6 +1088,8 @@ test "pass pointer to empty array initializer to anytype parameter" { |
| 1088 | 1088 | } |
| 1089 | 1089 | |
| 1090 | 1090 | test "initialize pointer to anyopaque with reference to empty array initializer" { |
| 1091 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 1092 | ||
| 1091 | 1093 | const ptr: *const anyopaque = &.{}; |
| 1092 | 1094 | // The above acts like an untyped initializer, since the `.{}` has no result type. |
| 1093 | 1095 | // So, `ptr` points in memory to an empty tuple (`@TypeOf(.{})`). |
test/behavior/cast.zig+10| ... | ... | @@ -9,6 +9,8 @@ const maxInt = std.math.maxInt; |
| 9 | 9 | const native_endian = builtin.target.cpu.arch.endian(); |
| 10 | 10 | |
| 11 | 11 | test "int to ptr cast" { |
| 12 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 13 | ||
| 12 | 14 | const x = @as(usize, 13); |
| 13 | 15 | const y = @as(*u8, @ptrFromInt(x)); |
| 14 | 16 | const z = @intFromPtr(y); |
| ... | ... | @@ -16,6 +18,8 @@ test "int to ptr cast" { |
| 16 | 18 | } |
| 17 | 19 | |
| 18 | 20 | test "integer literal to pointer cast" { |
| 21 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 22 | ||
| 19 | 23 | const vga_mem = @as(*u16, @ptrFromInt(0xB8000)); |
| 20 | 24 | try expect(@intFromPtr(vga_mem) == 0xB8000); |
| 21 | 25 | } |
| ... | ... | @@ -269,6 +273,8 @@ test "implicit cast from *[N]T to [*c]T" { |
| 269 | 273 | } |
| 270 | 274 | |
| 271 | 275 | test "*usize to *void" { |
| 276 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 277 | ||
| 272 | 278 | var i = @as(usize, 0); |
| 273 | 279 | const v: *void = @ptrCast(&i); |
| 274 | 280 | v.* = {}; |
| ... | ... | @@ -1481,6 +1487,8 @@ test "coerce between pointers of compatible differently-named floats" { |
| 1481 | 1487 | } |
| 1482 | 1488 | |
| 1483 | 1489 | test "peer type resolution of const and non-const pointer to array" { |
| 1490 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 1491 | ||
| 1484 | 1492 | const a = @as(*[1024]u8, @ptrFromInt(42)); |
| 1485 | 1493 | const b = @as(*const [1024]u8, @ptrFromInt(42)); |
| 1486 | 1494 | try std.testing.expect(@TypeOf(a, b) == *const [1024]u8); |
| ... | ... | @@ -1543,6 +1551,8 @@ test "optional pointer coerced to optional allowzero pointer" { |
| 1543 | 1551 | } |
| 1544 | 1552 | |
| 1545 | 1553 | test "optional slice coerced to allowzero many pointer" { |
| 1554 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 1555 | ||
| 1546 | 1556 | const a: ?[]const u32 = null; |
| 1547 | 1557 | const b: [*]allowzero const u8 = @ptrCast(a); |
| 1548 | 1558 | const c = @intFromPtr(b); |
test/behavior/comptime_memory.zig+6| ... | ... | @@ -406,6 +406,8 @@ test "mutate entire slice at comptime" { |
| 406 | 406 | } |
| 407 | 407 | |
| 408 | 408 | test "dereference undefined pointer to zero-bit type" { |
| 409 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 410 | ||
| 409 | 411 | const p0: *void = undefined; |
| 410 | 412 | try testing.expectEqual({}, p0.*); |
| 411 | 413 | |
| ... | ... | @@ -421,6 +423,8 @@ test "type pun extern struct" { |
| 421 | 423 | } |
| 422 | 424 | |
| 423 | 425 | test "type pun @ptrFromInt" { |
| 426 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 427 | ||
| 424 | 428 | const p: *u8 = @ptrFromInt(42); |
| 425 | 429 | // note that expectEqual hides the bug |
| 426 | 430 | try testing.expect(@as(*const [*]u8, @ptrCast(&p)).* == @as([*]u8, @ptrFromInt(42))); |
| ... | ... | @@ -511,6 +515,8 @@ fn fieldPtrTest() u32 { |
| 511 | 515 | return a.value; |
| 512 | 516 | } |
| 513 | 517 | test "pointer in aggregate field can mutate comptime state" { |
| 518 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 519 | ||
| 514 | 520 | try comptime std.testing.expect(fieldPtrTest() == 2); |
| 515 | 521 | } |
| 516 | 522 |
test/behavior/generics.zig+1| ... | ... | @@ -169,6 +169,7 @@ test "generic fn keeps non-generic parameter types" { |
| 169 | 169 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; |
| 170 | 170 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 171 | 171 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; |
| 172 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 172 | 173 | |
| 173 | 174 | const A = 128; |
| 174 | 175 |
test/behavior/pointers.zig+8| ... | ... | @@ -267,6 +267,8 @@ test "implicit cast error unions with non-optional to optional pointer" { |
| 267 | 267 | } |
| 268 | 268 | |
| 269 | 269 | test "compare equality of optional and non-optional pointer" { |
| 270 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 271 | ||
| 270 | 272 | const a = @as(*const usize, @ptrFromInt(0x12345678)); |
| 271 | 273 | const b = @as(?*usize, @ptrFromInt(0x12345678)); |
| 272 | 274 | try expect(a == b); |
| ... | ... | @@ -453,6 +455,8 @@ test "pointer sentinel with +inf" { |
| 453 | 455 | } |
| 454 | 456 | |
| 455 | 457 | test "pointer to array at fixed address" { |
| 458 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 459 | ||
| 456 | 460 | const array = @as(*volatile [2]u32, @ptrFromInt(0x10)); |
| 457 | 461 | // Silly check just to reference `array` |
| 458 | 462 | try expect(@intFromPtr(&array[0]) == 0x10); |
| ... | ... | @@ -494,6 +498,8 @@ test "pointer-integer arithmetic affects the alignment" { |
| 494 | 498 | } |
| 495 | 499 | |
| 496 | 500 | test "@intFromPtr on null optional at comptime" { |
| 501 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 502 | ||
| 497 | 503 | { |
| 498 | 504 | const pointer = @as(?*u8, @ptrFromInt(0x000)); |
| 499 | 505 | const x = @intFromPtr(pointer); |
| ... | ... | @@ -704,6 +710,8 @@ test "pointer-to-array constness for zero-size elements, const" { |
| 704 | 710 | } |
| 705 | 711 | |
| 706 | 712 | test "cast pointers with zero sized elements" { |
| 713 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 714 | ||
| 707 | 715 | const a: *void = undefined; |
| 708 | 716 | const b: *[1]void = a; |
| 709 | 717 | _ = b; |
test/behavior/ptrfromint.zig+1| ... | ... | @@ -44,6 +44,7 @@ test "@ptrFromInt creates null pointer" { |
| 44 | 44 | test "@ptrFromInt creates allowzero zero pointer" { |
| 45 | 45 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; |
| 46 | 46 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 47 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 47 | 48 | |
| 48 | 49 | const ptr = @as(*allowzero u32, @ptrFromInt(0)); |
| 49 | 50 | try expectEqual(@as(usize, 0), @intFromPtr(ptr)); |
test/behavior/slice.zig+2| ... | ... | @@ -238,6 +238,8 @@ test "slicing pointer by length" { |
| 238 | 238 | const x = @as([*]i32, @ptrFromInt(0x1000))[0..0x500]; |
| 239 | 239 | const y = x[0x100..]; |
| 240 | 240 | test "compile time slice of pointer to hard coded address" { |
| 241 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 242 | ||
| 241 | 243 | try expect(@intFromPtr(x) == 0x1000); |
| 242 | 244 | try expect(x.len == 0x500); |
| 243 | 245 |
test/behavior/struct.zig+4| ... | ... | @@ -1347,6 +1347,8 @@ test "struct field has a pointer to an aligned version of itself" { |
| 1347 | 1347 | } |
| 1348 | 1348 | |
| 1349 | 1349 | test "struct has only one reference" { |
| 1350 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 1351 | ||
| 1350 | 1352 | const S = struct { |
| 1351 | 1353 | fn optionalStructParam(_: ?struct { x: u8 }) void {} |
| 1352 | 1354 | fn errorUnionStructParam(_: error{}!struct { x: u8 }) void {} |
| ... | ... | @@ -1553,6 +1555,7 @@ test "struct field pointer has correct alignment" { |
| 1553 | 1555 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO |
| 1554 | 1556 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 1555 | 1557 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 1558 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 1556 | 1559 | |
| 1557 | 1560 | const S = struct { |
| 1558 | 1561 | fn doTheTest() !void { |
| ... | ... | @@ -1582,6 +1585,7 @@ test "extern struct field pointer has correct alignment" { |
| 1582 | 1585 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO |
| 1583 | 1586 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 1584 | 1587 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 1588 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 1585 | 1589 | |
| 1586 | 1590 | const S = struct { |
| 1587 | 1591 | fn doTheTest() !void { |
test/behavior/union.zig+2| ... | ... | @@ -1481,6 +1481,7 @@ test "defined-layout union field pointer has correct alignment" { |
| 1481 | 1481 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO |
| 1482 | 1482 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 1483 | 1483 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 1484 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 1484 | 1485 | |
| 1485 | 1486 | const S = struct { |
| 1486 | 1487 | fn doTheTest(comptime U: type) !void { |
| ... | ... | @@ -1515,6 +1516,7 @@ test "undefined-layout union field pointer has correct alignment" { |
| 1515 | 1516 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO |
| 1516 | 1517 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 1517 | 1518 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 1519 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 1518 | 1520 | |
| 1519 | 1521 | const S = struct { |
| 1520 | 1522 | fn doTheTest(comptime U: type) !void { |