| ... | @@ -112,10 +112,17 @@ pub const Class = enum(u3) { | ... | @@ -112,10 +112,17 @@ pub const Class = enum(u3) { |
| 112 | }; | 112 | }; |
| 113 | | 113 | |
| 114 | /// Returns the `Class` for the type `ty`. Asserts that the layout of `ty` is resolved. | 114 | /// Returns the `Class` for the type `ty`. Asserts that the layout of `ty` is resolved. |
| 115 | pub fn classify(ty: Type, zcu: *const Zcu) Class { | 115 | pub fn classify(start_ty: Type, zcu: *const Zcu) Class { |
| 116 | ty.assertHasLayout(zcu); | | |
| 117 | const ip = &zcu.intern_pool; | 116 | const ip = &zcu.intern_pool; |
| 118 | return switch (ip.indexToKey(ty.toIntern())) { | 117 | |
| | 118 | // We avoid recursion in most cases to make us more optimizer-friendly because this can be a |
| | 119 | // very hot code path. The only case where recursion is necessary is tuples, so that case is |
| | 120 | // outlined into a separate function; see `classifyTuple`. |
| | 121 | |
| | 122 | var extra_states: enum { none, one, many } = .none; |
| | 123 | |
| | 124 | var cur_ty = start_ty; |
| | 125 | const base: Class = while (true) break switch (ip.indexToKey(cur_ty.toIntern())) { |
| 119 | .simple_type => |t| switch (t) { | 126 | .simple_type => |t| switch (t) { |
| 120 | .f16, | 127 | .f16, |
| 121 | .f32, | 128 | .f32, |
| ... | @@ -165,17 +172,10 @@ pub fn classify(ty: Type, zcu: *const Zcu) Class { | ... | @@ -165,17 +172,10 @@ pub fn classify(ty: Type, zcu: *const Zcu) Class { |
| 165 | | 172 | |
| 166 | .opaque_type => .no_possible_value, | 173 | .opaque_type => .no_possible_value, |
| 167 | | 174 | |
| 168 | .error_union_type => |eu| switch (Type.fromInterned(eu.payload_type).classify(zcu)) { | 175 | .error_union_type => |eu| { |
| 169 | .no_possible_value, | 176 | extra_states = .many; |
| 170 | .one_possible_value, | 177 | cur_ty = .fromInterned(eu.payload_type); |
| 171 | .runtime, | 178 | continue; |
| 172 | => .runtime, | | |
| 173 | | | |
| 174 | .partially_comptime => .partially_comptime, | | |
| 175 | // It may seem that this should be `.partially_comptime` due to the error set, however | | |
| 176 | // there is no way to take a pointer to the error set of an error union, so it does not | | |
| 177 | // actually necessitate runtime bits. | | |
| 178 | .fully_comptime => .fully_comptime, | | |
| 179 | }, | 179 | }, |
| 180 | | 180 | |
| 181 | .int_type => |int| switch (int.bits) { | 181 | .int_type => |int| switch (int.bits) { |
| ... | @@ -183,55 +183,58 @@ pub fn classify(ty: Type, zcu: *const Zcu) Class { | ... | @@ -183,55 +183,58 @@ pub fn classify(ty: Type, zcu: *const Zcu) Class { |
| 183 | else => .runtime, | 183 | else => .runtime, |
| 184 | }, | 184 | }, |
| 185 | .array_type => |arr| { | 185 | .array_type => |arr| { |
| 186 | if (arr.len == 0 and arr.sentinel == .none) return .one_possible_value; | 186 | if (arr.len == 0 and arr.sentinel == .none) break .one_possible_value; |
| 187 | return Type.fromInterned(arr.child).classify(zcu); | 187 | cur_ty = .fromInterned(arr.child); |
| | 188 | continue; |
| 188 | }, | 189 | }, |
| 189 | .vector_type => |vec| { | 190 | .vector_type => |vec| { |
| 190 | if (vec.len == 0) return .one_possible_value; | 191 | if (vec.len == 0) break .one_possible_value; |
| 191 | return Type.fromInterned(vec.child).classify(zcu); | 192 | cur_ty = .fromInterned(vec.child); |
| | 193 | continue; |
| 192 | }, | 194 | }, |
| 193 | .opt_type => |child| switch (Type.fromInterned(child).classify(zcu)) { | 195 | .opt_type => |child_ty_ip| { |
| 194 | .no_possible_value => .one_possible_value, | 196 | extra_states = switch (extra_states) { |
| 195 | .one_possible_value => .runtime, | 197 | .none => .one, |
| 196 | else => |class| class, | 198 | .one, .many => .many, |
| | 199 | }; |
| | 200 | cur_ty = .fromInterned(child_ty_ip); |
| | 201 | continue; |
| 197 | }, | 202 | }, |
| 198 | .tuple_type => |tuple| { | 203 | .tuple_type => |tuple| { |
| 199 | var has_runtime_state = false; | 204 | @branchHint(.unlikely); |
| 200 | var has_comptime_state = false; | 205 | break classifyTuple(tuple.types.get(ip), tuple.values.get(ip), zcu); |
| 201 | for (tuple.types.get(ip), tuple.values.get(ip)) |field_ty, field_comptime_val| { | | |
| 202 | if (field_comptime_val != .none) continue; | | |
| 203 | switch (Type.fromInterned(field_ty).classify(zcu)) { | | |
| 204 | .no_possible_value => return .no_possible_value, | | |
| 205 | .one_possible_value => {}, | | |
| 206 | .runtime => has_runtime_state = true, | | |
| 207 | .fully_comptime => has_comptime_state = true, | | |
| 208 | .partially_comptime => { | | |
| 209 | has_runtime_state = true; | | |
| 210 | has_comptime_state = true; | | |
| 211 | }, | | |
| 212 | } | | |
| 213 | } | | |
| 214 | if (has_comptime_state) { | | |
| 215 | return if (has_runtime_state) .partially_comptime else .fully_comptime; | | |
| 216 | } else { | | |
| 217 | return if (has_runtime_state) .runtime else .one_possible_value; | | |
| 218 | } | | |
| 219 | }, | 206 | }, |
| 220 | .struct_type => { | 207 | .struct_type => { |
| 221 | const struct_obj = ip.loadStructType(ty.toIntern()); | 208 | const struct_obj = ip.loadStructType(cur_ty.toIntern()); |
| 222 | return switch (struct_obj.layout) { | 209 | switch (struct_obj.layout) { |
| 223 | .auto, .@"extern" => struct_obj.class, | 210 | .auto, .@"extern" => { |
| 224 | .@"packed" => Type.fromInterned(struct_obj.packed_backing_int_type).classify(zcu), | 211 | zcu.assertUpToDate(.wrap(.{ .type_layout = cur_ty.toIntern() })); |
| 225 | }; | 212 | break struct_obj.class; |
| | 213 | }, |
| | 214 | .@"packed" => { |
| | 215 | cur_ty = .fromInterned(struct_obj.packed_backing_int_type); |
| | 216 | continue; |
| | 217 | }, |
| | 218 | } |
| 226 | }, | 219 | }, |
| 227 | .union_type => { | 220 | .union_type => { |
| 228 | const union_obj = ip.loadUnionType(ty.toIntern()); | 221 | const union_obj = ip.loadUnionType(cur_ty.toIntern()); |
| 229 | return switch (union_obj.layout) { | 222 | switch (union_obj.layout) { |
| 230 | .auto, .@"extern" => union_obj.class, | 223 | .auto, .@"extern" => { |
| 231 | .@"packed" => Type.fromInterned(union_obj.packed_backing_int_type).classify(zcu), | 224 | zcu.assertUpToDate(.wrap(.{ .type_layout = cur_ty.toIntern() })); |
| 232 | }; | 225 | break union_obj.class; |
| | 226 | }, |
| | 227 | .@"packed" => { |
| | 228 | cur_ty = .fromInterned(union_obj.packed_backing_int_type); |
| | 229 | continue; |
| | 230 | }, |
| | 231 | } |
| | 232 | }, |
| | 233 | .enum_type => { |
| | 234 | zcu.assertUpToDate(.wrap(.{ .type_layout = cur_ty.toIntern() })); |
| | 235 | cur_ty = .fromInterned(ip.loadEnumType(cur_ty.toIntern()).int_tag_type); |
| | 236 | continue; |
| 233 | }, | 237 | }, |
| 234 | .enum_type => Type.fromInterned(ip.loadEnumType(ty.toIntern()).int_tag_type).classify(zcu), | | |
| 235 | | 238 | |
| 236 | // values, not types | 239 | // values, not types |
| 237 | .undef, | 240 | .undef, |
| ... | @@ -255,6 +258,53 @@ pub fn classify(ty: Type, zcu: *const Zcu) Class { | ... | @@ -255,6 +258,53 @@ pub fn classify(ty: Type, zcu: *const Zcu) Class { |
| 255 | .memoized_call, | 258 | .memoized_call, |
| 256 | => unreachable, | 259 | => unreachable, |
| 257 | }; | 260 | }; |
| | 261 | |
| | 262 | return switch (base) { |
| | 263 | .runtime => .runtime, // extra states are irrelevant, we already have many! |
| | 264 | .partially_comptime => .partially_comptime, // likewise |
| | 265 | .fully_comptime => { |
| | 266 | // We do not need to change to `.partially_comptime` here because the extra states do |
| | 267 | // not necessarily require runtime bits. This is because Zig does not provide a way to |
| | 268 | // take the address of the "is null" bit of an optional or the error set "inside" of an |
| | 269 | // error union. |
| | 270 | return .fully_comptime; |
| | 271 | }, |
| | 272 | |
| | 273 | .no_possible_value => switch (extra_states) { |
| | 274 | .none => .no_possible_value, |
| | 275 | .one => .one_possible_value, |
| | 276 | .many => .runtime, |
| | 277 | }, |
| | 278 | |
| | 279 | .one_possible_value => switch (extra_states) { |
| | 280 | .none => .one_possible_value, |
| | 281 | .one, .many => .runtime, |
| | 282 | }, |
| | 283 | }; |
| | 284 | } |
| | 285 | /// This is a separate function to `classify` to avoid recursion in the main `classify` function, |
| | 286 | /// which can encourage the optimizer to e.g. inline `classify` where it would be beneficial. |
| | 287 | fn classifyTuple(types: []const InternPool.Index, values: []const InternPool.Index, zcu: *const Zcu) Class { |
| | 288 | var has_runtime_state = false; |
| | 289 | var has_comptime_state = false; |
| | 290 | for (types, values) |field_ty, field_comptime_val| { |
| | 291 | if (field_comptime_val != .none) continue; |
| | 292 | switch (Type.fromInterned(field_ty).classify(zcu)) { |
| | 293 | .no_possible_value => return .no_possible_value, |
| | 294 | .one_possible_value => {}, |
| | 295 | .runtime => has_runtime_state = true, |
| | 296 | .fully_comptime => has_comptime_state = true, |
| | 297 | .partially_comptime => { |
| | 298 | has_runtime_state = true; |
| | 299 | has_comptime_state = true; |
| | 300 | }, |
| | 301 | } |
| | 302 | } |
| | 303 | if (has_comptime_state) { |
| | 304 | return if (has_runtime_state) .partially_comptime else .fully_comptime; |
| | 305 | } else { |
| | 306 | return if (has_runtime_state) .runtime else .one_possible_value; |
| | 307 | } |
| 258 | } | 308 | } |
| 259 | | 309 | |
| 260 | /// Asserts the type is resolved. | 310 | /// Asserts the type is resolved. |
| ... | @@ -1061,7 +1111,10 @@ pub fn abiSize(ty: Type, zcu: *const Zcu) u64 { | ... | @@ -1061,7 +1111,10 @@ pub fn abiSize(ty: Type, zcu: *const Zcu) u64 { |
| 1061 | .error_union_type => |error_union| { | 1111 | .error_union_type => |error_union| { |
| 1062 | const payload_ty: Type = .fromInterned(error_union.payload_type); | 1112 | const payload_ty: Type = .fromInterned(error_union.payload_type); |
| 1063 | switch (payload_ty.classify(zcu)) { | 1113 | switch (payload_ty.classify(zcu)) { |
| 1064 | .fully_comptime => return 0, // error set does not require runtime bits, see comment in `classify` | 1114 | // Zig has no way to take the address of the error set "in" an error union (giving |
| | 1115 | // implementations more freedom in terms of data layout), so if the payload type is |
| | 1116 | // fully comptime, we don't need to dedicate runtime bits to the error set. |
| | 1117 | .fully_comptime => return 0, |
| 1065 | else => {}, | 1118 | else => {}, |
| 1066 | } | 1119 | } |
| 1067 | // The layout will either be (code, payload, padding) or (payload, code, padding) | 1120 | // The layout will either be (code, payload, padding) or (payload, code, padding) |
| ... | @@ -3177,6 +3230,12 @@ fn validateExternCallconv(cc: std.builtin.CallingConvention) bool { | ... | @@ -3177,6 +3230,12 @@ fn validateExternCallconv(cc: std.builtin.CallingConvention) bool { |
| 3177 | | 3230 | |
| 3178 | /// Asserts that `ty` has resolved layout. | 3231 | /// Asserts that `ty` has resolved layout. |
| 3179 | pub fn assertHasLayout(ty: Type, zcu: *const Zcu) void { | 3232 | pub fn assertHasLayout(ty: Type, zcu: *const Zcu) void { |
| | 3233 | if (!std.debug.runtime_safety) { |
| | 3234 | // This early exit isn't necessary (`Zcu.assertUpToDate` checks `std.debug.runtime_safety` |
| | 3235 | // itself), but LLVM has been observed to fail at optimizing away this safety check, which |
| | 3236 | // has a major performance impact on ReleaseFast compiler builds. |
| | 3237 | return; |
| | 3238 | } |
| 3180 | switch (zcu.intern_pool.indexToKey(ty.toIntern())) { | 3239 | switch (zcu.intern_pool.indexToKey(ty.toIntern())) { |
| 3181 | .int_type, | 3240 | .int_type, |
| 3182 | .ptr_type, | 3241 | .ptr_type, |