From 6e72026e3b04586cf44deadeddeb8bf44a42ee99 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 15 Jun 2025 11:36:15 -0400 Subject: [PATCH] Legalize: make the feature set comptime-known in zig1 This allows legalizations to be added that aren't used by zig1 without affecting the size of zig1. --- src/Air/Legalize.zig | 101 ++++++++++++++++++++++++++++--------------- src/codegen/c.zig | 19 +++++--- 2 files changed, 78 insertions(+), 42 deletions(-) diff --git a/src/Air/Legalize.zig b/src/Air/Legalize.zig index 01304d78e8..c0b0d9b76c 100644 --- a/src/Air/Legalize.zig +++ b/src/Air/Legalize.zig @@ -1,7 +1,36 @@ pt: Zcu.PerThread, air_instructions: std.MultiArrayList(Air.Inst), air_extra: std.ArrayListUnmanaged(u32), -features: *const Features, +features: if (switch (dev.env) { + .bootstrap => @import("../codegen/c.zig").legalizeFeatures(undefined), + else => null, +}) |bootstrap_features| struct { + fn init(features: *const Features) @This() { + assert(features.eql(bootstrap_features.*)); + return .{}; + } + /// `inline` to propagate comptime-known result. + inline fn has(_: @This(), comptime feature: Feature) bool { + return comptime bootstrap_features.contains(feature); + } + /// `inline` to propagate comptime-known result. + fn hasAny(_: @This(), comptime features: []const Feature) bool { + return comptime !bootstrap_features.intersectWith(.initMany(features)).eql(.initEmpty()); + } +} else struct { + features: *const Features, + /// `inline` to propagate whether `dev.check` returns. + inline fn init(features: *const Features) @This() { + dev.check(.legalize); + return .{ .features = features }; + } + fn has(rt: @This(), comptime feature: Feature) bool { + return rt.features.contains(feature); + } + fn hasAny(rt: @This(), comptime features: []const Feature) bool { + return !rt.features.intersectWith(comptime .initMany(features)).eql(comptime .initEmpty()); + } +}, pub const Feature = enum { scalarize_add, @@ -199,7 +228,7 @@ pub const Feature = enum { .float_from_int => .scalarize_float_from_int, .shuffle_one => .scalarize_shuffle_one, .shuffle_two => .scalarize_shuffle_two, - .select => .scalarize_selects, + .select => .scalarize_select, .mul_add => .scalarize_mul_add, }; } @@ -210,13 +239,12 @@ pub const Features = std.enums.EnumSet(Feature); pub const Error = std.mem.Allocator.Error; pub fn legalize(air: *Air, pt: Zcu.PerThread, features: *const Features) Error!void { - dev.check(.legalize); assert(!features.eql(comptime .initEmpty())); // backend asked to run legalize, but no features were enabled var l: Legalize = .{ .pt = pt, .air_instructions = air.instructions.toMultiArrayList(), .air_extra = air.extra, - .features = features, + .features = .init(features), }; defer air.* = l.getTmpAir(); const main_extra = l.extraData(Air.Block, l.air_extra.items[@intFromEnum(Air.ExtraIndex.main_block)]); @@ -278,28 +306,28 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .bit_and, .bit_or, .xor, - => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { + => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); }, - .add_safe => if (l.features.contains(.expand_add_safe)) { - assert(!l.features.contains(.scalarize_add_safe)); // it doesn't make sense to do both + .add_safe => if (l.features.has(.expand_add_safe)) { + assert(!l.features.has(.scalarize_add_safe)); // it doesn't make sense to do both continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .add_with_overflow)); - } else if (l.features.contains(.scalarize_add_safe)) { + } else if (l.features.has(.scalarize_add_safe)) { const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); }, - .sub_safe => if (l.features.contains(.expand_sub_safe)) { - assert(!l.features.contains(.scalarize_sub_safe)); // it doesn't make sense to do both + .sub_safe => if (l.features.has(.expand_sub_safe)) { + assert(!l.features.has(.scalarize_sub_safe)); // it doesn't make sense to do both continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .sub_with_overflow)); - } else if (l.features.contains(.scalarize_sub_safe)) { + } else if (l.features.has(.scalarize_sub_safe)) { const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); }, - .mul_safe => if (l.features.contains(.expand_mul_safe)) { - assert(!l.features.contains(.scalarize_mul_safe)); // it doesn't make sense to do both + .mul_safe => if (l.features.has(.expand_mul_safe)) { + assert(!l.features.has(.scalarize_mul_safe)); // it doesn't make sense to do both continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .mul_with_overflow)); - } else if (l.features.contains(.scalarize_mul_safe)) { + } else if (l.features.has(.scalarize_mul_safe)) { const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); }, @@ -308,7 +336,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .sub_with_overflow, .mul_with_overflow, .shl_with_overflow, - => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { + => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; if (ty_pl.ty.toType().fieldType(0, zcu).isVector(zcu)) continue :inst l.replaceInst(inst, .block, try l.scalarizeOverflowBlockPayload(inst)); }, @@ -320,13 +348,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .shl, .shl_exact, .shl_sat, - => |air_tag| if (!l.features.intersectWith(comptime .initMany(&.{ + => |air_tag| if (l.features.hasAny(&.{ .unsplat_shift_rhs, .scalarize(air_tag), - })).eql(comptime .initEmpty())) { + })) { const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; if (l.typeOf(bin_op.rhs).isVector(zcu)) { - if (l.features.contains(.unsplat_shift_rhs)) { + if (l.features.has(.unsplat_shift_rhs)) { if (bin_op.rhs.toInterned()) |rhs_ip_index| switch (ip.indexToKey(rhs_ip_index)) { else => {}, .aggregate => |aggregate| switch (aggregate.storage) { @@ -347,7 +375,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { } } } - if (l.features.contains(comptime .scalarize(air_tag))) continue :inst try l.scalarize(inst, .bin_op); + if (l.features.has(comptime .scalarize(air_tag))) continue :inst try l.scalarize(inst, .bin_op); } }, inline .not, @@ -364,11 +392,11 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .int_from_float, .int_from_float_optimized, .float_from_int, - => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { + => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op); }, - .bitcast => if (l.features.contains(.scalarize_bitcast)) { + .bitcast => if (l.features.has(.scalarize_bitcast)) { const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; const to_ty = ty_op.ty.toType(); @@ -404,10 +432,10 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { }; if (!from_ty_legal) continue :inst l.replaceInst(inst, .block, try l.scalarizeBitcastOperandBlockPayload(inst)); }, - .intcast_safe => if (l.features.contains(.expand_intcast_safe)) { - assert(!l.features.contains(.scalarize_intcast_safe)); // it doesn't make sense to do both + .intcast_safe => if (l.features.has(.expand_intcast_safe)) { + assert(!l.features.has(.scalarize_intcast_safe)); // it doesn't make sense to do both continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst)); - } else if (l.features.contains(.scalarize_intcast_safe)) { + } else if (l.features.has(.scalarize_intcast_safe)) { const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op); }, @@ -442,7 +470,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .trunc_float, .neg, .neg_optimized, - => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { + => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { const un_op = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op; if (l.typeOf(un_op).isVector(zcu)) continue :inst try l.scalarize(inst, .un_op); }, @@ -459,7 +487,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .cmp_neq, .cmp_neq_optimized, => {}, - inline .cmp_vector, .cmp_vector_optimized => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { + inline .cmp_vector, .cmp_vector_optimized => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) { const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; if (ty_pl.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .cmp_vector); }, @@ -513,13 +541,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .bool_and, .bool_or, => {}, - .load => if (l.features.contains(.expand_packed_load)) { + .load => if (l.features.has(.expand_packed_load)) { const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; const ptr_info = l.typeOf(ty_op.operand).ptrInfo(zcu); if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedLoadBlockPayload(inst)); }, .ret, .ret_safe, .ret_load => {}, - .store, .store_safe => if (l.features.contains(.expand_packed_store)) { + .store, .store_safe => if (l.features.has(.expand_packed_store)) { const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; const ptr_info = l.typeOf(bin_op.lhs).ptrInfo(zcu); if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedStoreBlockPayload(inst)); @@ -542,7 +570,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .struct_field_ptr_index_2, .struct_field_ptr_index_3, => {}, - .struct_field_val => if (l.features.contains(.expand_packed_struct_field_val)) { + .struct_field_val => if (l.features.has(.expand_packed_struct_field_val)) { const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = l.extraData(Air.StructField, ty_pl.payload).data; switch (l.typeOf(extra.struct_operand).containerLayout(zcu)) { @@ -564,7 +592,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .ptr_elem_ptr, .array_to_slice, => {}, - .reduce, .reduce_optimized => if (l.features.contains(.reduce_one_elem_to_bitcast)) { + .reduce, .reduce_optimized => if (l.features.has(.reduce_one_elem_to_bitcast)) { const reduce = l.air_instructions.items(.data)[@intFromEnum(inst)].reduce; const vector_ty = l.typeOf(reduce.operand); switch (vector_ty.vectorLen(zcu)) { @@ -577,9 +605,9 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { } }, .splat => {}, - .shuffle_one => if (l.features.contains(.scalarize_shuffle_one)) continue :inst try l.scalarize(inst, .shuffle_one), - .shuffle_two => if (l.features.contains(.scalarize_shuffle_two)) continue :inst try l.scalarize(inst, .shuffle_two), - .select => if (l.features.contains(.scalarize_select)) continue :inst try l.scalarize(inst, .select), + .shuffle_one => if (l.features.has(.scalarize_shuffle_one)) continue :inst try l.scalarize(inst, .shuffle_one), + .shuffle_two => if (l.features.has(.scalarize_shuffle_two)) continue :inst try l.scalarize(inst, .shuffle_two), + .select => if (l.features.has(.scalarize_select)) continue :inst try l.scalarize(inst, .select), .memset, .memset_safe, .memcpy, @@ -597,7 +625,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .error_name, .error_set_has_value, => {}, - .aggregate_init => if (l.features.contains(.expand_packed_aggregate_init)) { + .aggregate_init => if (l.features.has(.expand_packed_aggregate_init)) { const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; const agg_ty = ty_pl.ty.toType(); switch (agg_ty.zigTypeTag(zcu)) { @@ -609,7 +637,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { } }, .union_init, .prefetch => {}, - .mul_add => if (l.features.contains(.scalarize_mul_add)) { + .mul_add => if (l.features.has(.scalarize_mul_add)) { const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op; if (l.typeOf(pl_op.operand).isVector(zcu)) continue :inst try l.scalarize(inst, .pl_op_bin); }, @@ -636,6 +664,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { } const ScalarizeForm = enum { un_op, ty_op, bin_op, pl_op_bin, bitcast, cmp_vector, shuffle_one, shuffle_two, select }; +/// inline to propagate comptime-known `replaceInst` result. inline fn scalarize(l: *Legalize, orig_inst: Air.Inst.Index, comptime form: ScalarizeForm) Error!Air.Inst.Tag { return l.replaceInst(orig_inst, .block, try l.scalarizeBlockPayload(orig_inst, form)); } @@ -2691,7 +2720,7 @@ fn addBlockBody(l: *Legalize, body: []const Air.Inst.Index) Error!u32 { } /// Returns `tag` to remind the caller to `continue :inst` the result. -/// This is inline to propagate the comptime-known `tag`. +/// `inline` to propagate the comptime-known `tag` result. inline fn replaceInst(l: *Legalize, inst: Air.Inst.Index, comptime tag: Air.Inst.Tag, data: Air.Inst.Data) Air.Inst.Tag { const orig_ty = if (std.debug.runtime_safety) l.typeOfIndex(inst) else {}; l.air_instructions.set(@intFromEnum(inst), .{ .tag = tag, .data = data }); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index f4952d4a58..0fe3f80919 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -23,12 +23,19 @@ const BigIntLimb = std.math.big.Limb; const BigInt = std.math.big.int; pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features { - return if (dev.env.supports(.legalize)) comptime &.initMany(&.{ - .expand_intcast_safe, - .expand_add_safe, - .expand_sub_safe, - .expand_mul_safe, - }) else null; // we don't currently ask zig1 to use safe optimization modes + return comptime switch (dev.env.supports(.legalize)) { + inline false, true => |supports_legalize| &.init(.{ + // we don't currently ask zig1 to use safe optimization modes + .expand_intcast_safe = supports_legalize, + .expand_add_safe = supports_legalize, + .expand_sub_safe = supports_legalize, + .expand_mul_safe = supports_legalize, + + .expand_packed_load = true, + .expand_packed_store = true, + .expand_packed_struct_field_val = true, + }), + }; } /// For most backends, MIR is basically a sequence of machine code instructions, perhaps with some