| ... | ... | @@ -112,10 +112,17 @@ pub const Class = enum(u3) { |
| 112 | 112 | }; |
| 113 | 113 | |
| 114 | 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 { |
| 116 | | ty.assertHasLayout(zcu); |
| 115 | pub fn classify(start_ty: Type, zcu: *const Zcu) Class { |
| 117 | 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 | 126 | .simple_type => |t| switch (t) { |
| 120 | 127 | .f16, |
| 121 | 128 | .f32, |
| ... | ... | @@ -165,17 +172,10 @@ pub fn classify(ty: Type, zcu: *const Zcu) Class { |
| 165 | 172 | |
| 166 | 173 | .opaque_type => .no_possible_value, |
| 167 | 174 | |
| 168 | | .error_union_type => |eu| switch (Type.fromInterned(eu.payload_type).classify(zcu)) { |
| 169 | | .no_possible_value, |
| 170 | | .one_possible_value, |
| 171 | | .runtime, |
| 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, |
| 175 | .error_union_type => |eu| { |
| 176 | extra_states = .many; |
| 177 | cur_ty = .fromInterned(eu.payload_type); |
| 178 | continue; |
| 179 | 179 | }, |
| 180 | 180 | |
| 181 | 181 | .int_type => |int| switch (int.bits) { |
| ... | ... | @@ -183,55 +183,58 @@ pub fn classify(ty: Type, zcu: *const Zcu) Class { |
| 183 | 183 | else => .runtime, |
| 184 | 184 | }, |
| 185 | 185 | .array_type => |arr| { |
| 186 | | if (arr.len == 0 and arr.sentinel == .none) return .one_possible_value; |
| 187 | | return Type.fromInterned(arr.child).classify(zcu); |
| 186 | if (arr.len == 0 and arr.sentinel == .none) break .one_possible_value; |
| 187 | cur_ty = .fromInterned(arr.child); |
| 188 | continue; |
| 188 | 189 | }, |
| 189 | 190 | .vector_type => |vec| { |
| 190 | | if (vec.len == 0) return .one_possible_value; |
| 191 | | return Type.fromInterned(vec.child).classify(zcu); |
| 191 | if (vec.len == 0) break .one_possible_value; |
| 192 | cur_ty = .fromInterned(vec.child); |
| 193 | continue; |
| 192 | 194 | }, |
| 193 | | .opt_type => |child| switch (Type.fromInterned(child).classify(zcu)) { |
| 194 | | .no_possible_value => .one_possible_value, |
| 195 | | .one_possible_value => .runtime, |
| 196 | | else => |class| class, |
| 195 | .opt_type => |child_ty_ip| { |
| 196 | extra_states = switch (extra_states) { |
| 197 | .none => .one, |
| 198 | .one, .many => .many, |
| 199 | }; |
| 200 | cur_ty = .fromInterned(child_ty_ip); |
| 201 | continue; |
| 197 | 202 | }, |
| 198 | 203 | .tuple_type => |tuple| { |
| 199 | | var has_runtime_state = false; |
| 200 | | var has_comptime_state = false; |
| 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 | | } |
| 204 | @branchHint(.unlikely); |
| 205 | break classifyTuple(tuple.types.get(ip), tuple.values.get(ip), zcu); |
| 219 | 206 | }, |
| 220 | 207 | .struct_type => { |
| 221 | | const struct_obj = ip.loadStructType(ty.toIntern()); |
| 222 | | return switch (struct_obj.layout) { |
| 223 | | .auto, .@"extern" => struct_obj.class, |
| 224 | | .@"packed" => Type.fromInterned(struct_obj.packed_backing_int_type).classify(zcu), |
| 225 | | }; |
| 208 | const struct_obj = ip.loadStructType(cur_ty.toIntern()); |
| 209 | switch (struct_obj.layout) { |
| 210 | .auto, .@"extern" => { |
| 211 | zcu.assertUpToDate(.wrap(.{ .type_layout = cur_ty.toIntern() })); |
| 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 | 220 | .union_type => { |
| 228 | | const union_obj = ip.loadUnionType(ty.toIntern()); |
| 229 | | return switch (union_obj.layout) { |
| 230 | | .auto, .@"extern" => union_obj.class, |
| 231 | | .@"packed" => Type.fromInterned(union_obj.packed_backing_int_type).classify(zcu), |
| 232 | | }; |
| 221 | const union_obj = ip.loadUnionType(cur_ty.toIntern()); |
| 222 | switch (union_obj.layout) { |
| 223 | .auto, .@"extern" => { |
| 224 | zcu.assertUpToDate(.wrap(.{ .type_layout = cur_ty.toIntern() })); |
| 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 | 239 | // values, not types |
| 237 | 240 | .undef, |
| ... | ... | @@ -255,6 +258,53 @@ pub fn classify(ty: Type, zcu: *const Zcu) Class { |
| 255 | 258 | .memoized_call, |
| 256 | 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 | 310 | /// Asserts the type is resolved. |
| ... | ... | @@ -1061,7 +1111,10 @@ pub fn abiSize(ty: Type, zcu: *const Zcu) u64 { |
| 1061 | 1111 | .error_union_type => |error_union| { |
| 1062 | 1112 | const payload_ty: Type = .fromInterned(error_union.payload_type); |
| 1063 | 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 | 1118 | else => {}, |
| 1066 | 1119 | } |
| 1067 | 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 | 3230 | |
| 3178 | 3231 | /// Asserts that `ty` has resolved layout. |
| 3179 | 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 | 3239 | switch (zcu.intern_pool.indexToKey(ty.toIntern())) { |
| 3181 | 3240 | .int_type, |
| 3182 | 3241 | .ptr_type, |