diff --git a/BRANCH_TODO b/BRANCH_TODO new file mode 100644 index 0000000000000000000000000000000000000000..ed77d6bd03dc1a2e56d324e5d825a7c1640bba25 --- /dev/null +++ b/BRANCH_TODO @@ -0,0 +1,725 @@ + * be sure to test debug info of parameters + + + /// Each bit represents the index of an `Inst` parameter in the `args` field. + /// If a bit is set, it marks the end of the lifetime of the corresponding + /// instruction parameter. For example, 0b101 means that the first and + /// third `Inst` parameters' lifetimes end after this instruction, and will + /// not have any more following references. + /// The most significant bit being set means that the instruction itself is + /// never referenced, in other words its lifetime ends as soon as it finishes. + /// If bit 15 (0b1xxx_xxxx_xxxx_xxxx) is set, it means this instruction itself is unreferenced. + /// If bit 14 (0bx1xx_xxxx_xxxx_xxxx) is set, it means this is a special case and the + /// lifetimes of operands are encoded elsewhere. + deaths: DeathsInt = undefined, + + + pub const DeathsInt = u16; + pub const DeathsBitIndex = std.math.Log2Int(DeathsInt); + pub const unreferenced_bit_index = @typeInfo(DeathsInt).Int.bits - 1; + pub const deaths_bits = unreferenced_bit_index - 1; + + pub fn isUnused(self: Inst) bool { + return (self.deaths & (1 << unreferenced_bit_index)) != 0; + } + + pub fn operandDies(self: Inst, index: DeathsBitIndex) bool { + assert(index < deaths_bits); + return @truncate(u1, self.deaths >> index) != 0; + } + + pub fn clearOperandDeath(self: *Inst, index: DeathsBitIndex) void { + assert(index < deaths_bits); + self.deaths &= ~(@as(DeathsInt, 1) << index); + } + + pub fn specialOperandDeaths(self: Inst) bool { + return (self.deaths & (1 << deaths_bits)) != 0; + } + + pub fn operandCount(base: *Inst) usize { + inline for (@typeInfo(Tag).Enum.fields) |field| { + const tag = @intToEnum(Tag, field.value); + if (tag == base.tag) { + return @fieldParentPtr(tag.Type(), "base", base).operandCount(); + } + } + unreachable; + } + + pub fn getOperand(base: *Inst, index: usize) ?*Inst { + inline for (@typeInfo(Tag).Enum.fields) |field| { + const tag = @intToEnum(Tag, field.value); + if (tag == base.tag) { + return @fieldParentPtr(tag.Type(), "base", base).getOperand(index); + } + } + unreachable; + } + + pub fn Type(tag: Tag) type { + return switch (tag) { + .alloc, + .retvoid, + .unreach, + .breakpoint, + => NoOp, + + .ref, + .ret, + .bitcast, + .not, + .is_non_null, + .is_non_null_ptr, + .is_null, + .is_null_ptr, + .is_err, + .is_non_err, + .is_err_ptr, + .is_non_err_ptr, + .ptrtoint, + .floatcast, + .intcast, + .load, + .optional_payload, + .optional_payload_ptr, + .wrap_optional, + .unwrap_errunion_payload, + .unwrap_errunion_err, + .unwrap_errunion_payload_ptr, + .unwrap_errunion_err_ptr, + .wrap_errunion_payload, + .wrap_errunion_err, + => UnOp, + + .add, + .addwrap, + .sub, + .subwrap, + .mul, + .mulwrap, + .div, + .cmp_lt, + .cmp_lte, + .cmp_eq, + .cmp_gte, + .cmp_gt, + .cmp_neq, + .store, + .bool_and, + .bool_or, + .bit_and, + .bit_or, + .xor, + => BinOp, + + .arg => Arg, + .assembly => Assembly, + .block => Block, + .br => Br, + .br_block_flat => BrBlockFlat, + .br_void => BrVoid, + .call => Call, + .condbr => CondBr, + .constant => Constant, + .loop => Loop, + .varptr => VarPtr, + .struct_field_ptr => StructFieldPtr, + .switchbr => SwitchBr, + .dbg_stmt => DbgStmt, + }; + } + + pub fn Args(comptime T: type) type { + return std.meta.fieldInfo(T, .args).field_type; + } + + /// Returns `null` if runtime-known. + /// Should be called by codegen, not by Sema. Sema functions should call + /// `resolvePossiblyUndefinedValue` or `resolveDefinedValue` instead. + /// TODO audit Sema code for violations to the above guidance. + pub fn value(base: *Inst) ?Value { + if (base.ty.onePossibleValue()) |opv| return opv; + + const inst = base.castTag(.constant) orelse return null; + return inst.val; + } + + pub fn breakBlock(base: *Inst) ?*Block { + return switch (base.tag) { + .br => base.castTag(.br).?.block, + .br_void => base.castTag(.br_void).?.block, + .br_block_flat => base.castTag(.br_block_flat).?.block, + else => null, + }; + } + + pub const convertable_br_size = std.math.max(@sizeOf(BrBlockFlat), @sizeOf(Br)); + pub const convertable_br_align = std.math.max(@alignOf(BrBlockFlat), @alignOf(Br)); + comptime { + assert(@offsetOf(BrBlockFlat, "base") == @offsetOf(Br, "base")); + } + + pub const BrBlockFlat = struct { + pub const base_tag = Tag.br_block_flat; + + base: Inst, + block: *Block, + body: Body, + + pub fn operandCount(self: *const BrBlockFlat) usize { + _ = self; + return 0; + } + pub fn getOperand(self: *const BrBlockFlat, index: usize) ?*Inst { + _ = self; + _ = index; + return null; + } + }; + + /// Same as `br` except the operand is a list of instructions to be treated as + /// a flat block; that is there is only 1 break instruction from the block, and + /// it is implied to be after the last instruction, and the last instruction is + /// the break operand. + /// This instruction exists for late-stage semantic analysis patch ups, to + /// replace one br operand with multiple instructions, without moving anything else around. + br_block_flat, + + + + pub const Assembly = struct { + pub const base_tag = Tag.assembly; + + base: Inst, + asm_source: []const u8, + is_volatile: bool, + output_constraint: ?[]const u8, + inputs: []const []const u8, + clobbers: []const []const u8, + args: []const *Inst, + + pub fn operandCount(self: *const Assembly) usize { + return self.args.len; + } + pub fn getOperand(self: *const Assembly, index: usize) ?*Inst { + if (index < self.args.len) + return self.args[index]; + return null; + } + }; + + pub const StructFieldPtr = struct { + struct_ptr: *Inst, + field_index: usize, + }; + + +/// For debugging purposes, prints a function representation to stderr. +pub fn dumpFn(old_module: Module, module_fn: *Module.Fn) void { + const allocator = old_module.gpa; + var ctx: DumpAir = .{ + .allocator = allocator, + .arena = std.heap.ArenaAllocator.init(allocator), + .old_module = &old_module, + .module_fn = module_fn, + .indent = 2, + .inst_table = DumpAir.InstTable.init(allocator), + .partial_inst_table = DumpAir.InstTable.init(allocator), + .const_table = DumpAir.InstTable.init(allocator), + }; + defer ctx.inst_table.deinit(); + defer ctx.partial_inst_table.deinit(); + defer ctx.const_table.deinit(); + defer ctx.arena.deinit(); + + switch (module_fn.state) { + .queued => std.debug.print("(queued)", .{}), + .inline_only => std.debug.print("(inline_only)", .{}), + .in_progress => std.debug.print("(in_progress)", .{}), + .sema_failure => std.debug.print("(sema_failure)", .{}), + .dependency_failure => std.debug.print("(dependency_failure)", .{}), + .success => { + const writer = std.io.getStdErr().writer(); + ctx.dump(module_fn.body, writer) catch @panic("failed to dump AIR"); + }, + } +} + +const DumpAir = struct { + allocator: *std.mem.Allocator, + arena: std.heap.ArenaAllocator, + old_module: *const Module, + module_fn: *Module.Fn, + indent: usize, + inst_table: InstTable, + partial_inst_table: InstTable, + const_table: InstTable, + next_index: usize = 0, + next_partial_index: usize = 0, + next_const_index: usize = 0, + + const InstTable = std.AutoArrayHashMap(*Inst, usize); + + /// TODO: Improve this code to include a stack of Body and store the instructions + /// in there. Now we are putting all the instructions in a function local table, + /// however instructions that are in a Body can be thown away when the Body ends. + fn dump(dtz: *DumpAir, body: Body, writer: std.fs.File.Writer) !void { + // First pass to pre-populate the table so that we can show even invalid references. + // Must iterate the same order we iterate the second time. + // We also look for constants and put them in the const_table. + try dtz.fetchInstsAndResolveConsts(body); + + std.debug.print("Module.Function(name={s}):\n", .{dtz.module_fn.owner_decl.name}); + + var it = dtz.const_table.iterator(); + while (it.next()) |entry| { + const constant = entry.key_ptr.*.castTag(.constant).?; + try writer.print(" @{d}: {} = {};\n", .{ + entry.value_ptr.*, constant.base.ty, constant.val, + }); + } + + return dtz.dumpBody(body, writer); + } + + fn fetchInstsAndResolveConsts(dtz: *DumpAir, body: Body) error{OutOfMemory}!void { + for (body.instructions) |inst| { + try dtz.inst_table.put(inst, dtz.next_index); + dtz.next_index += 1; + switch (inst.tag) { + .alloc, + .retvoid, + .unreach, + .breakpoint, + .dbg_stmt, + .arg, + => {}, + + .ref, + .ret, + .bitcast, + .not, + .is_non_null, + .is_non_null_ptr, + .is_null, + .is_null_ptr, + .is_err, + .is_non_err, + .is_err_ptr, + .is_non_err_ptr, + .ptrtoint, + .floatcast, + .intcast, + .load, + .optional_payload, + .optional_payload_ptr, + .wrap_optional, + .wrap_errunion_payload, + .wrap_errunion_err, + .unwrap_errunion_payload, + .unwrap_errunion_err, + .unwrap_errunion_payload_ptr, + .unwrap_errunion_err_ptr, + => { + const un_op = inst.cast(Inst.UnOp).?; + try dtz.findConst(un_op.operand); + }, + + .add, + .addwrap, + .sub, + .subwrap, + .mul, + .mulwrap, + .div, + .cmp_lt, + .cmp_lte, + .cmp_eq, + .cmp_gte, + .cmp_gt, + .cmp_neq, + .store, + .bool_and, + .bool_or, + .bit_and, + .bit_or, + .xor, + => { + const bin_op = inst.cast(Inst.BinOp).?; + try dtz.findConst(bin_op.lhs); + try dtz.findConst(bin_op.rhs); + }, + + .br => { + const br = inst.castTag(.br).?; + try dtz.findConst(&br.block.base); + try dtz.findConst(br.operand); + }, + + .br_block_flat => { + const br_block_flat = inst.castTag(.br_block_flat).?; + try dtz.findConst(&br_block_flat.block.base); + try dtz.fetchInstsAndResolveConsts(br_block_flat.body); + }, + + .br_void => { + const br_void = inst.castTag(.br_void).?; + try dtz.findConst(&br_void.block.base); + }, + + .block => { + const block = inst.castTag(.block).?; + try dtz.fetchInstsAndResolveConsts(block.body); + }, + + .condbr => { + const condbr = inst.castTag(.condbr).?; + try dtz.findConst(condbr.condition); + try dtz.fetchInstsAndResolveConsts(condbr.then_body); + try dtz.fetchInstsAndResolveConsts(condbr.else_body); + }, + .switchbr => { + const switchbr = inst.castTag(.switchbr).?; + try dtz.findConst(switchbr.target); + try dtz.fetchInstsAndResolveConsts(switchbr.else_body); + for (switchbr.cases) |case| { + try dtz.fetchInstsAndResolveConsts(case.body); + } + }, + + .loop => { + const loop = inst.castTag(.loop).?; + try dtz.fetchInstsAndResolveConsts(loop.body); + }, + .call => { + const call = inst.castTag(.call).?; + try dtz.findConst(call.func); + for (call.args) |arg| { + try dtz.findConst(arg); + } + }, + .struct_field_ptr => { + const struct_field_ptr = inst.castTag(.struct_field_ptr).?; + try dtz.findConst(struct_field_ptr.struct_ptr); + }, + + // TODO fill out this debug printing + .assembly, + .constant, + .varptr, + => {}, + } + } + } + + fn dumpBody(dtz: *DumpAir, body: Body, writer: std.fs.File.Writer) (std.fs.File.WriteError || error{OutOfMemory})!void { + for (body.instructions) |inst| { + const my_index = dtz.next_partial_index; + try dtz.partial_inst_table.put(inst, my_index); + dtz.next_partial_index += 1; + + try writer.writeByteNTimes(' ', dtz.indent); + try writer.print("%{d}: {} = {s}(", .{ + my_index, inst.ty, @tagName(inst.tag), + }); + switch (inst.tag) { + .alloc, + .retvoid, + .unreach, + .breakpoint, + .dbg_stmt, + => try writer.writeAll(")\n"), + + .ref, + .ret, + .bitcast, + .not, + .is_non_null, + .is_non_null_ptr, + .is_null, + .is_null_ptr, + .is_err, + .is_err_ptr, + .is_non_err, + .is_non_err_ptr, + .ptrtoint, + .floatcast, + .intcast, + .load, + .optional_payload, + .optional_payload_ptr, + .wrap_optional, + .wrap_errunion_err, + .wrap_errunion_payload, + .unwrap_errunion_err, + .unwrap_errunion_payload, + .unwrap_errunion_payload_ptr, + .unwrap_errunion_err_ptr, + => { + const un_op = inst.cast(Inst.UnOp).?; + const kinky = try dtz.writeInst(writer, un_op.operand); + if (kinky != null) { + try writer.writeAll(") // Instruction does not dominate all uses!\n"); + } else { + try writer.writeAll(")\n"); + } + }, + + .add, + .addwrap, + .sub, + .subwrap, + .mul, + .mulwrap, + .div, + .cmp_lt, + .cmp_lte, + .cmp_eq, + .cmp_gte, + .cmp_gt, + .cmp_neq, + .store, + .bool_and, + .bool_or, + .bit_and, + .bit_or, + .xor, + => { + const bin_op = inst.cast(Inst.BinOp).?; + + const lhs_kinky = try dtz.writeInst(writer, bin_op.lhs); + try writer.writeAll(", "); + const rhs_kinky = try dtz.writeInst(writer, bin_op.rhs); + + if (lhs_kinky != null or rhs_kinky != null) { + try writer.writeAll(") // Instruction does not dominate all uses!"); + if (lhs_kinky) |lhs| { + try writer.print(" %{d}", .{lhs}); + } + if (rhs_kinky) |rhs| { + try writer.print(" %{d}", .{rhs}); + } + try writer.writeAll("\n"); + } else { + try writer.writeAll(")\n"); + } + }, + + .arg => { + const arg = inst.castTag(.arg).?; + try writer.print("{s})\n", .{arg.name}); + }, + + .br => { + const br = inst.castTag(.br).?; + + const lhs_kinky = try dtz.writeInst(writer, &br.block.base); + try writer.writeAll(", "); + const rhs_kinky = try dtz.writeInst(writer, br.operand); + + if (lhs_kinky != null or rhs_kinky != null) { + try writer.writeAll(") // Instruction does not dominate all uses!"); + if (lhs_kinky) |lhs| { + try writer.print(" %{d}", .{lhs}); + } + if (rhs_kinky) |rhs| { + try writer.print(" %{d}", .{rhs}); + } + try writer.writeAll("\n"); + } else { + try writer.writeAll(")\n"); + } + }, + + .br_block_flat => { + const br_block_flat = inst.castTag(.br_block_flat).?; + const block_kinky = try dtz.writeInst(writer, &br_block_flat.block.base); + if (block_kinky != null) { + try writer.writeAll(", { // Instruction does not dominate all uses!\n"); + } else { + try writer.writeAll(", {\n"); + } + + const old_indent = dtz.indent; + dtz.indent += 2; + try dtz.dumpBody(br_block_flat.body, writer); + dtz.indent = old_indent; + + try writer.writeByteNTimes(' ', dtz.indent); + try writer.writeAll("})\n"); + }, + + .br_void => { + const br_void = inst.castTag(.br_void).?; + const kinky = try dtz.writeInst(writer, &br_void.block.base); + if (kinky) |_| { + try writer.writeAll(") // Instruction does not dominate all uses!\n"); + } else { + try writer.writeAll(")\n"); + } + }, + + .block => { + const block = inst.castTag(.block).?; + + try writer.writeAll("{\n"); + + const old_indent = dtz.indent; + dtz.indent += 2; + try dtz.dumpBody(block.body, writer); + dtz.indent = old_indent; + + try writer.writeByteNTimes(' ', dtz.indent); + try writer.writeAll("})\n"); + }, + + .condbr => { + const condbr = inst.castTag(.condbr).?; + + const condition_kinky = try dtz.writeInst(writer, condbr.condition); + if (condition_kinky != null) { + try writer.writeAll(", { // Instruction does not dominate all uses!\n"); + } else { + try writer.writeAll(", {\n"); + } + + const old_indent = dtz.indent; + dtz.indent += 2; + try dtz.dumpBody(condbr.then_body, writer); + + try writer.writeByteNTimes(' ', old_indent); + try writer.writeAll("}, {\n"); + + try dtz.dumpBody(condbr.else_body, writer); + dtz.indent = old_indent; + + try writer.writeByteNTimes(' ', old_indent); + try writer.writeAll("})\n"); + }, + + .switchbr => { + const switchbr = inst.castTag(.switchbr).?; + + const condition_kinky = try dtz.writeInst(writer, switchbr.target); + if (condition_kinky != null) { + try writer.writeAll(", { // Instruction does not dominate all uses!\n"); + } else { + try writer.writeAll(", {\n"); + } + const old_indent = dtz.indent; + + if (switchbr.else_body.instructions.len != 0) { + dtz.indent += 2; + try dtz.dumpBody(switchbr.else_body, writer); + + try writer.writeByteNTimes(' ', old_indent); + try writer.writeAll("}, {\n"); + dtz.indent = old_indent; + } + for (switchbr.cases) |case| { + dtz.indent += 2; + try dtz.dumpBody(case.body, writer); + + try writer.writeByteNTimes(' ', old_indent); + try writer.writeAll("}, {\n"); + dtz.indent = old_indent; + } + + try writer.writeByteNTimes(' ', old_indent); + try writer.writeAll("})\n"); + }, + + .loop => { + const loop = inst.castTag(.loop).?; + + try writer.writeAll("{\n"); + + const old_indent = dtz.indent; + dtz.indent += 2; + try dtz.dumpBody(loop.body, writer); + dtz.indent = old_indent; + + try writer.writeByteNTimes(' ', dtz.indent); + try writer.writeAll("})\n"); + }, + + .call => { + const call = inst.castTag(.call).?; + + const args_kinky = try dtz.allocator.alloc(?usize, call.args.len); + defer dtz.allocator.free(args_kinky); + std.mem.set(?usize, args_kinky, null); + var any_kinky_args = false; + + const func_kinky = try dtz.writeInst(writer, call.func); + + for (call.args) |arg, i| { + try writer.writeAll(", "); + + args_kinky[i] = try dtz.writeInst(writer, arg); + any_kinky_args = any_kinky_args or args_kinky[i] != null; + } + + if (func_kinky != null or any_kinky_args) { + try writer.writeAll(") // Instruction does not dominate all uses!"); + if (func_kinky) |func_index| { + try writer.print(" %{d}", .{func_index}); + } + for (args_kinky) |arg_kinky| { + if (arg_kinky) |arg_index| { + try writer.print(" %{d}", .{arg_index}); + } + } + try writer.writeAll("\n"); + } else { + try writer.writeAll(")\n"); + } + }, + + .struct_field_ptr => { + const struct_field_ptr = inst.castTag(.struct_field_ptr).?; + const kinky = try dtz.writeInst(writer, struct_field_ptr.struct_ptr); + if (kinky != null) { + try writer.print("{d}) // Instruction does not dominate all uses!\n", .{ + struct_field_ptr.field_index, + }); + } else { + try writer.print("{d})\n", .{struct_field_ptr.field_index}); + } + }, + + // TODO fill out this debug printing + .assembly, + .constant, + .varptr, + => { + try writer.writeAll("!TODO!)\n"); + }, + } + } + } + + fn writeInst(dtz: *DumpAir, writer: std.fs.File.Writer, inst: *Inst) !?usize { + if (dtz.partial_inst_table.get(inst)) |operand_index| { + try writer.print("%{d}", .{operand_index}); + return null; + } else if (dtz.const_table.get(inst)) |operand_index| { + try writer.print("@{d}", .{operand_index}); + return null; + } else if (dtz.inst_table.get(inst)) |operand_index| { + try writer.print("%{d}", .{operand_index}); + return operand_index; + } else { + try writer.writeAll("!BADREF!"); + return null; + } + } + + fn findConst(dtz: *DumpAir, operand: *Inst) !void { + if (operand.tag == .constant) { + try dtz.const_table.put(operand, dtz.next_const_index); + dtz.next_const_index += 1; + } + } +}; diff --git a/CMakeLists.txt b/CMakeLists.txt index 44417e4159ba8b1a64f4e6e44cf3832584bce2a1..39db11773c8f6c48024416a34e43230d4e328e1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -564,7 +564,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/codegen/x86_64.zig" "${CMAKE_SOURCE_DIR}/src/glibc.zig" "${CMAKE_SOURCE_DIR}/src/introspect.zig" - "${CMAKE_SOURCE_DIR}/src/air.zig" + "${CMAKE_SOURCE_DIR}/src/Air.zig" "${CMAKE_SOURCE_DIR}/src/libc_installation.zig" "${CMAKE_SOURCE_DIR}/src/libcxx.zig" "${CMAKE_SOURCE_DIR}/src/libtsan.zig" diff --git a/src/Air.zig b/src/Air.zig new file mode 100644 index 0000000000000000000000000000000000000000..97a5824abcc3b23b476ad0b6057f60ded3bdf02e --- /dev/null +++ b/src/Air.zig @@ -0,0 +1,335 @@ +//! Analyzed Intermediate Representation. +//! Sema inputs ZIR and outputs AIR. + +const std = @import("std"); +const Value = @import("value.zig").Value; +const Type = @import("type.zig").Type; +const Module = @import("Module.zig"); +const assert = std.debug.assert; +const Air = @This(); + +instructions: std.MultiArrayList(Inst).Slice, +/// The meaning of this data is determined by `Inst.Tag` value. +extra: []u32, +values: []Value, +variables: []*Module.Var, + +pub const Inst = struct { + tag: Tag, + data: Data, + + pub const Tag = enum(u8) { + /// Float or integer addition. For integers, wrapping is undefined behavior. + /// Result type is the same as both operands. + /// Uses the `bin_op` field. + add, + /// Integer addition. Wrapping is defined to be twos complement wrapping. + /// Result type is the same as both operands. + /// Uses the `bin_op` field. + addwrap, + /// Float or integer subtraction. For integers, wrapping is undefined behavior. + /// Result type is the same as both operands. + /// Uses the `bin_op` field. + sub, + /// Integer subtraction. Wrapping is defined to be twos complement wrapping. + /// Result type is the same as both operands. + /// Uses the `bin_op` field. + subwrap, + /// Float or integer multiplication. For integers, wrapping is undefined behavior. + /// Result type is the same as both operands. + /// Uses the `bin_op` field. + mul, + /// Integer multiplication. Wrapping is defined to be twos complement wrapping. + /// Result type is the same as both operands. + /// Uses the `bin_op` field. + mulwrap, + /// Integer or float division. For integers, wrapping is undefined behavior. + /// Result type is the same as both operands. + /// Uses the `bin_op` field. + div, + /// Allocates stack local memory. + /// Uses the `ty` field. + alloc, + /// TODO + assembly, + /// Bitwise AND. `&`. + /// Result type is the same as both operands. + /// Uses the `bin_op` field. + bit_and, + /// Bitwise OR. `|`. + /// Result type is the same as both operands. + /// Uses the `bin_op` field. + bit_or, + /// Bitwise XOR. `^` + /// Uses the `bin_op` field. + xor, + /// Boolean or binary NOT. + /// Uses the `ty_op` field. + not, + /// Reinterpret the memory representation of a value as a different type. + /// Uses the `ty_op` field. + bitcast, + /// Uses the `ty_pl` field with payload `Block`. + block, + /// Return from a block with a result. + /// Result type is always noreturn. + /// Uses the `br` field. + br, + /// Lowers to a hardware trap instruction, or the next best thing. + /// Result type is always void. + breakpoint, + /// Function call. + /// Result type is the return type of the function being called. + /// Uses the `pl_op` field with the `Call` payload. operand is the callee. + call, + /// `<`. Result type is always bool. + /// Uses the `bin_op` field. + cmp_lt, + /// `<=`. Result type is always bool. + /// Uses the `bin_op` field. + cmp_lte, + /// `==`. Result type is always bool. + /// Uses the `bin_op` field. + cmp_eq, + /// `>=`. Result type is always bool. + /// Uses the `bin_op` field. + cmp_gte, + /// `>`. Result type is always bool. + /// Uses the `bin_op` field. + cmp_gt, + /// `!=`. Result type is always bool. + /// Uses the `bin_op` field. + cmp_neq, + /// Conditional branch. + /// Result type is always noreturn. + /// Uses the `pl_op` field. Operand is the condition. Payload is `CondBr`. + cond_br, + /// Switch branch. + /// Result type is always noreturn. + /// Uses the `pl_op` field. Operand is the condition. Payload is `SwitchBr`. + switch_br, + /// A comptime-known value. Uses the `ty_pl` field, payload is index of + /// `values` array. + constant, + /// Notes the beginning of a source code statement and marks the line and column. + /// Result type is always void. + /// Uses the `dbg_stmt` field. + dbg_stmt, + /// ?T => bool + /// Result type is always bool. + /// Uses the `un_op` field. + is_null, + /// ?T => bool (inverted logic) + /// Result type is always bool. + /// Uses the `un_op` field. + is_non_null, + /// *?T => bool + /// Result type is always bool. + /// Uses the `un_op` field. + is_null_ptr, + /// *?T => bool (inverted logic) + /// Result type is always bool. + /// Uses the `un_op` field. + is_non_null_ptr, + /// E!T => bool + /// Result type is always bool. + /// Uses the `un_op` field. + is_err, + /// E!T => bool (inverted logic) + /// Result type is always bool. + /// Uses the `un_op` field. + is_non_err, + /// *E!T => bool + /// Result type is always bool. + /// Uses the `un_op` field. + is_err_ptr, + /// *E!T => bool (inverted logic) + /// Result type is always bool. + /// Uses the `un_op` field. + is_non_err_ptr, + /// Result type is always bool. + /// Uses the `bin_op` field. + bool_and, + /// Result type is always bool. + /// Uses the `bin_op` field. + bool_or, + /// Read a value from a pointer. + /// Uses the `ty_op` field. + load, + /// A labeled block of code that loops forever. At the end of the body it is implied + /// to repeat; no explicit "repeat" instruction terminates loop bodies. + /// Result type is always noreturn. + /// Uses the `ty_pl` field. Payload is `Block`. + loop, + /// Converts a pointer to its address. Result type is always `usize`. + /// Uses the `un_op` field. + ptrtoint, + /// Stores a value onto the stack and returns a pointer to it. + /// TODO audit where this AIR instruction is emitted, maybe it should instead be emitting + /// alloca instruction and storing to the alloca. + /// Uses the `ty_op` field. + ref, + /// Return a value from a function. + /// Result type is always noreturn. + /// Uses the `un_op` field. + ret, + /// Returns a pointer to a global variable. + /// Uses the `ty_pl` field. Index is into the `variables` array. + varptr, + /// Write a value to a pointer. LHS is pointer, RHS is value. + /// Result type is always void. + /// Uses the `bin_op` field. + store, + /// Indicates the program counter will never get to this instruction. + /// Result type is always noreturn. + unreach, + /// Convert from one float type to another. + /// Uses the `ty_op` field. + floatcast, + /// TODO audit uses of this. We should have explicit instructions for integer + /// widening and truncating. + /// Uses the `ty_op` field. + intcast, + /// ?T => T. If the value is null, undefined behavior. + /// Uses the `ty_op` field. + optional_payload, + /// *?T => *T. If the value is null, undefined behavior. + /// Uses the `ty_op` field. + optional_payload_ptr, + /// Given a payload value, wraps it in an optional type. + /// Uses the `ty_op` field. + wrap_optional, + /// E!T -> T. If the value is an error, undefined behavior. + /// Uses the `ty_op` field. + unwrap_errunion_payload, + /// E!T -> E. If the value is not an error, undefined behavior. + /// Uses the `ty_op` field. + unwrap_errunion_err, + /// *(E!T) -> *T. If the value is an error, undefined behavior. + /// Uses the `ty_op` field. + unwrap_errunion_payload_ptr, + /// *(E!T) -> E. If the value is not an error, undefined behavior. + /// Uses the `ty_op` field. + unwrap_errunion_err_ptr, + /// wrap from T to E!T + /// Uses the `ty_op` field. + wrap_errunion_payload, + /// wrap from E to E!T + /// Uses the `ty_op` field. + wrap_errunion_err, + /// Given a pointer to a struct and a field index, returns a pointer to the field. + /// Uses the `ty_pl` field, payload is `StructField`. + struct_field_ptr, + + pub fn fromCmpOp(op: std.math.CompareOperator) Tag { + return switch (op) { + .lt => .cmp_lt, + .lte => .cmp_lte, + .eq => .cmp_eq, + .gte => .cmp_gte, + .gt => .cmp_gt, + .neq => .cmp_neq, + }; + } + }; + + /// The position of an AIR instruction within the `Air` instructions array. + pub const Index = u32; + + /// All instructions have an 8-byte payload, which is contained within + /// this union. `Tag` determines which union field is active, as well as + /// how to interpret the data within. + pub const Data = union { + un_op: Ref, + bin_op: struct { + lhs: Ref, + rhs: Ref, + }, + ty: Type, + ty_op: struct { + ty: Ref, + operand: Ref, + }, + ty_pl: struct { + ty: Ref, + // Index into a different array. + payload: u32, + }, + br: struct { + block_inst: Index, + operand: Ref, + }, + pl_op: struct { + operand: Ref, + payload: u32, + }, + constant: struct { + ty: Type, + val: Value, + }, + dbg_stmt: struct { + line: u32, + column: u32, + }, + + // Make sure we don't accidentally add a field to make this union + // bigger than expected. Note that in Debug builds, Zig is allowed + // to insert a secret field for safety checks. + comptime { + if (std.builtin.mode != .Debug) { + assert(@sizeOf(Data) == 8); + } + } + }; + + pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator { + return switch (base.tag) { + .cmp_lt => .lt, + .cmp_lte => .lte, + .cmp_eq => .eq, + .cmp_gte => .gte, + .cmp_gt => .gt, + .cmp_neq => .neq, + else => null, + }; + } + + /// Trailing is a list of instruction indexes for every `body_len`. + pub const Block = struct { + body_len: u32, + }; + + /// Trailing is a list of `Ref` for every `args_len`. + pub const Call = struct { + args_len: u32, + }; + + /// This data is stored inside extra, with two sets of trailing `Ref`: + /// * 0. the then body, according to `then_body_len`. + /// * 1. the else body, according to `else_body_len`. + pub const CondBr = struct { + condition: Ref, + then_body_len: u32, + else_body_len: u32, + }; + + /// Trailing: + /// * 0. `Case` for each `cases_len` + /// * 1. the else body, according to `else_body_len`. + pub const SwitchBr = struct { + cases_len: u32, + else_body_len: u32, + + /// Trailing: + /// * instruction index for each `body_len`. + pub const Case = struct { + item: Ref, + body_len: u32, + }; + }; + + pub const StructField = struct { + struct_ptr: Ref, + field_index: u32, + }; +}; diff --git a/src/Module.zig b/src/Module.zig index a1f6887fbd9d03793f03e4c87a16ac399e21ea8c..2f1dc0b33b2bdb00dcbf39b94570c9648da38032 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -21,7 +21,7 @@ const Type = @import("type.zig").Type; const TypedValue = @import("TypedValue.zig"); const Package = @import("Package.zig"); const link = @import("link.zig"); -const ir = @import("air.zig"); +const Air = @import("Air.zig"); const Zir = @import("Zir.zig"); const trace = @import("tracy.zig").trace; const AstGen = @import("AstGen.zig"); diff --git a/src/Sema.zig b/src/Sema.zig index d7ce9fdf4f02bd7b9a9894eaec79047a17553500..85cb4aa423475998f6488822b20da3a050942181 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -52,7 +52,7 @@ const Sema = @This(); const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const TypedValue = @import("TypedValue.zig"); -const ir = @import("air.zig"); +const Air = @import("Air.zig"); const Zir = @import("Zir.zig"); const Module = @import("Module.zig"); const Inst = ir.Inst; diff --git a/src/Zir.zig b/src/Zir.zig index db851cfa4bced9d7ee242f64ae284fd008866593..b975500e2f4eb5b08879a2112e660055b4e17990 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -22,7 +22,6 @@ const Zir = @This(); const Type = @import("type.zig").Type; const Value = @import("value.zig").Value; const TypedValue = @import("TypedValue.zig"); -const ir = @import("air.zig"); const Module = @import("Module.zig"); const LazySrcLoc = Module.LazySrcLoc; @@ -214,7 +213,7 @@ pub const Inst = struct { as_node, /// Bitwise AND. `&` bit_and, - /// Bitcast a value to a different type. + /// Reinterpret the memory representation of a value as a different type. /// Uses the pl_node field with payload `Bin`. bitcast, /// A typed result location pointer is bitcasted to a new result location pointer. diff --git a/src/air.zig b/src/air.zig deleted file mode 100644 index e73367945b04c3a3d4b5f7ea5260ff16b2b5264b..0000000000000000000000000000000000000000 --- a/src/air.zig +++ /dev/null @@ -1,1185 +0,0 @@ -const std = @import("std"); -const Value = @import("value.zig").Value; -const Type = @import("type.zig").Type; -const Module = @import("Module.zig"); -const assert = std.debug.assert; -const codegen = @import("codegen.zig"); -const ast = std.zig.ast; - -/// These are in-memory, analyzed instructions. See `zir.Inst` for the representation -/// of instructions that correspond to the ZIR text format. -/// This struct owns the `Value` and `Type` memory. When the struct is deallocated, -/// so are the `Value` and `Type`. The value of a constant must be copied into -/// a memory location for the value to survive after a const instruction. -pub const Inst = struct { - tag: Tag, - /// Each bit represents the index of an `Inst` parameter in the `args` field. - /// If a bit is set, it marks the end of the lifetime of the corresponding - /// instruction parameter. For example, 0b101 means that the first and - /// third `Inst` parameters' lifetimes end after this instruction, and will - /// not have any more following references. - /// The most significant bit being set means that the instruction itself is - /// never referenced, in other words its lifetime ends as soon as it finishes. - /// If bit 15 (0b1xxx_xxxx_xxxx_xxxx) is set, it means this instruction itself is unreferenced. - /// If bit 14 (0bx1xx_xxxx_xxxx_xxxx) is set, it means this is a special case and the - /// lifetimes of operands are encoded elsewhere. - deaths: DeathsInt = undefined, - ty: Type, - src: Module.LazySrcLoc, - - pub const DeathsInt = u16; - pub const DeathsBitIndex = std.math.Log2Int(DeathsInt); - pub const unreferenced_bit_index = @typeInfo(DeathsInt).Int.bits - 1; - pub const deaths_bits = unreferenced_bit_index - 1; - - pub fn isUnused(self: Inst) bool { - return (self.deaths & (1 << unreferenced_bit_index)) != 0; - } - - pub fn operandDies(self: Inst, index: DeathsBitIndex) bool { - assert(index < deaths_bits); - return @truncate(u1, self.deaths >> index) != 0; - } - - pub fn clearOperandDeath(self: *Inst, index: DeathsBitIndex) void { - assert(index < deaths_bits); - self.deaths &= ~(@as(DeathsInt, 1) << index); - } - - pub fn specialOperandDeaths(self: Inst) bool { - return (self.deaths & (1 << deaths_bits)) != 0; - } - - pub const Tag = enum { - add, - addwrap, - alloc, - arg, - assembly, - bit_and, - bitcast, - bit_or, - block, - br, - /// Same as `br` except the operand is a list of instructions to be treated as - /// a flat block; that is there is only 1 break instruction from the block, and - /// it is implied to be after the last instruction, and the last instruction is - /// the break operand. - /// This instruction exists for late-stage semantic analysis patch ups, to - /// replace one br operand with multiple instructions, without moving anything else around. - br_block_flat, - breakpoint, - br_void, - call, - cmp_lt, - cmp_lte, - cmp_eq, - cmp_gte, - cmp_gt, - cmp_neq, - condbr, - constant, - dbg_stmt, - /// ?T => bool - is_null, - /// ?T => bool (inverted logic) - is_non_null, - /// *?T => bool - is_null_ptr, - /// *?T => bool (inverted logic) - is_non_null_ptr, - /// E!T => bool - is_err, - /// E!T => bool (inverted logic) - is_non_err, - /// *E!T => bool - is_err_ptr, - /// *E!T => bool (inverted logic) - is_non_err_ptr, - bool_and, - bool_or, - /// Read a value from a pointer. - load, - /// A labeled block of code that loops forever. At the end of the body it is implied - /// to repeat; no explicit "repeat" instruction terminates loop bodies. - loop, - ptrtoint, - ref, - ret, - retvoid, - varptr, - /// Write a value to a pointer. LHS is pointer, RHS is value. - store, - sub, - subwrap, - unreach, - mul, - mulwrap, - div, - not, - floatcast, - intcast, - /// ?T => T - optional_payload, - /// *?T => *T - optional_payload_ptr, - wrap_optional, - /// E!T -> T - unwrap_errunion_payload, - /// E!T -> E - unwrap_errunion_err, - /// *(E!T) -> *T - unwrap_errunion_payload_ptr, - /// *(E!T) -> E - unwrap_errunion_err_ptr, - /// wrap from T to E!T - wrap_errunion_payload, - /// wrap from E to E!T - wrap_errunion_err, - xor, - switchbr, - /// Given a pointer to a struct and a field index, returns a pointer to the field. - struct_field_ptr, - - pub fn Type(tag: Tag) type { - return switch (tag) { - .alloc, - .retvoid, - .unreach, - .breakpoint, - => NoOp, - - .ref, - .ret, - .bitcast, - .not, - .is_non_null, - .is_non_null_ptr, - .is_null, - .is_null_ptr, - .is_err, - .is_non_err, - .is_err_ptr, - .is_non_err_ptr, - .ptrtoint, - .floatcast, - .intcast, - .load, - .optional_payload, - .optional_payload_ptr, - .wrap_optional, - .unwrap_errunion_payload, - .unwrap_errunion_err, - .unwrap_errunion_payload_ptr, - .unwrap_errunion_err_ptr, - .wrap_errunion_payload, - .wrap_errunion_err, - => UnOp, - - .add, - .addwrap, - .sub, - .subwrap, - .mul, - .mulwrap, - .div, - .cmp_lt, - .cmp_lte, - .cmp_eq, - .cmp_gte, - .cmp_gt, - .cmp_neq, - .store, - .bool_and, - .bool_or, - .bit_and, - .bit_or, - .xor, - => BinOp, - - .arg => Arg, - .assembly => Assembly, - .block => Block, - .br => Br, - .br_block_flat => BrBlockFlat, - .br_void => BrVoid, - .call => Call, - .condbr => CondBr, - .constant => Constant, - .loop => Loop, - .varptr => VarPtr, - .struct_field_ptr => StructFieldPtr, - .switchbr => SwitchBr, - .dbg_stmt => DbgStmt, - }; - } - - pub fn fromCmpOp(op: std.math.CompareOperator) Tag { - return switch (op) { - .lt => .cmp_lt, - .lte => .cmp_lte, - .eq => .cmp_eq, - .gte => .cmp_gte, - .gt => .cmp_gt, - .neq => .cmp_neq, - }; - } - }; - - /// Prefer `castTag` to this. - pub fn cast(base: *Inst, comptime T: type) ?*T { - if (@hasField(T, "base_tag")) { - return base.castTag(T.base_tag); - } - inline for (@typeInfo(Tag).Enum.fields) |field| { - const tag = @intToEnum(Tag, field.value); - if (base.tag == tag) { - if (T == tag.Type()) { - return @fieldParentPtr(T, "base", base); - } - return null; - } - } - unreachable; - } - - pub fn castTag(base: *Inst, comptime tag: Tag) ?*tag.Type() { - if (base.tag == tag) { - return @fieldParentPtr(tag.Type(), "base", base); - } - return null; - } - - pub fn Args(comptime T: type) type { - return std.meta.fieldInfo(T, .args).field_type; - } - - /// Returns `null` if runtime-known. - /// Should be called by codegen, not by Sema. Sema functions should call - /// `resolvePossiblyUndefinedValue` or `resolveDefinedValue` instead. - /// TODO audit Sema code for violations to the above guidance. - pub fn value(base: *Inst) ?Value { - if (base.ty.onePossibleValue()) |opv| return opv; - - const inst = base.castTag(.constant) orelse return null; - return inst.val; - } - - pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator { - return switch (base.tag) { - .cmp_lt => .lt, - .cmp_lte => .lte, - .cmp_eq => .eq, - .cmp_gte => .gte, - .cmp_gt => .gt, - .cmp_neq => .neq, - else => null, - }; - } - - pub fn operandCount(base: *Inst) usize { - inline for (@typeInfo(Tag).Enum.fields) |field| { - const tag = @intToEnum(Tag, field.value); - if (tag == base.tag) { - return @fieldParentPtr(tag.Type(), "base", base).operandCount(); - } - } - unreachable; - } - - pub fn getOperand(base: *Inst, index: usize) ?*Inst { - inline for (@typeInfo(Tag).Enum.fields) |field| { - const tag = @intToEnum(Tag, field.value); - if (tag == base.tag) { - return @fieldParentPtr(tag.Type(), "base", base).getOperand(index); - } - } - unreachable; - } - - pub fn breakBlock(base: *Inst) ?*Block { - return switch (base.tag) { - .br => base.castTag(.br).?.block, - .br_void => base.castTag(.br_void).?.block, - .br_block_flat => base.castTag(.br_block_flat).?.block, - else => null, - }; - } - - pub const NoOp = struct { - base: Inst, - - pub fn operandCount(self: *const NoOp) usize { - _ = self; - return 0; - } - pub fn getOperand(self: *const NoOp, index: usize) ?*Inst { - _ = self; - _ = index; - return null; - } - }; - - pub const UnOp = struct { - base: Inst, - operand: *Inst, - - pub fn operandCount(self: *const UnOp) usize { - _ = self; - return 1; - } - pub fn getOperand(self: *const UnOp, index: usize) ?*Inst { - if (index == 0) - return self.operand; - return null; - } - }; - - pub const BinOp = struct { - base: Inst, - lhs: *Inst, - rhs: *Inst, - - pub fn operandCount(self: *const BinOp) usize { - _ = self; - return 2; - } - pub fn getOperand(self: *const BinOp, index: usize) ?*Inst { - var i = index; - - if (i < 1) - return self.lhs; - i -= 1; - - if (i < 1) - return self.rhs; - i -= 1; - - return null; - } - }; - - pub const Arg = struct { - pub const base_tag = Tag.arg; - - base: Inst, - /// This exists to be emitted into debug info. - name: [*:0]const u8, - - pub fn operandCount(self: *const Arg) usize { - _ = self; - return 0; - } - pub fn getOperand(self: *const Arg, index: usize) ?*Inst { - _ = self; - _ = index; - return null; - } - }; - - pub const Assembly = struct { - pub const base_tag = Tag.assembly; - - base: Inst, - asm_source: []const u8, - is_volatile: bool, - output_constraint: ?[]const u8, - inputs: []const []const u8, - clobbers: []const []const u8, - args: []const *Inst, - - pub fn operandCount(self: *const Assembly) usize { - return self.args.len; - } - pub fn getOperand(self: *const Assembly, index: usize) ?*Inst { - if (index < self.args.len) - return self.args[index]; - return null; - } - }; - - pub const Block = struct { - pub const base_tag = Tag.block; - - base: Inst, - body: Body, - - pub fn operandCount(self: *const Block) usize { - _ = self; - return 0; - } - pub fn getOperand(self: *const Block, index: usize) ?*Inst { - _ = self; - _ = index; - return null; - } - }; - - pub const convertable_br_size = std.math.max(@sizeOf(BrBlockFlat), @sizeOf(Br)); - pub const convertable_br_align = std.math.max(@alignOf(BrBlockFlat), @alignOf(Br)); - comptime { - assert(@offsetOf(BrBlockFlat, "base") == @offsetOf(Br, "base")); - } - - pub const BrBlockFlat = struct { - pub const base_tag = Tag.br_block_flat; - - base: Inst, - block: *Block, - body: Body, - - pub fn operandCount(self: *const BrBlockFlat) usize { - _ = self; - return 0; - } - pub fn getOperand(self: *const BrBlockFlat, index: usize) ?*Inst { - _ = self; - _ = index; - return null; - } - }; - - pub const Br = struct { - pub const base_tag = Tag.br; - - base: Inst, - block: *Block, - operand: *Inst, - - pub fn operandCount(self: *const Br) usize { - _ = self; - return 1; - } - pub fn getOperand(self: *const Br, index: usize) ?*Inst { - _ = self; - if (index == 0) - return self.operand; - return null; - } - }; - - pub const BrVoid = struct { - pub const base_tag = Tag.br_void; - - base: Inst, - block: *Block, - - pub fn operandCount(self: *const BrVoid) usize { - _ = self; - return 0; - } - pub fn getOperand(self: *const BrVoid, index: usize) ?*Inst { - _ = self; - _ = index; - return null; - } - }; - - pub const Call = struct { - pub const base_tag = Tag.call; - - base: Inst, - func: *Inst, - args: []const *Inst, - - pub fn operandCount(self: *const Call) usize { - return self.args.len + 1; - } - pub fn getOperand(self: *const Call, index: usize) ?*Inst { - var i = index; - - if (i < 1) - return self.func; - i -= 1; - - if (i < self.args.len) - return self.args[i]; - i -= self.args.len; - - return null; - } - }; - - pub const CondBr = struct { - pub const base_tag = Tag.condbr; - - base: Inst, - condition: *Inst, - then_body: Body, - else_body: Body, - /// Set of instructions whose lifetimes end at the start of one of the branches. - /// The `then` branch is first: `deaths[0..then_death_count]`. - /// The `else` branch is next: `(deaths + then_death_count)[0..else_death_count]`. - deaths: [*]*Inst = undefined, - then_death_count: u32 = 0, - else_death_count: u32 = 0, - - pub fn operandCount(self: *const CondBr) usize { - _ = self; - return 1; - } - pub fn getOperand(self: *const CondBr, index: usize) ?*Inst { - var i = index; - - if (i < 1) - return self.condition; - i -= 1; - - return null; - } - pub fn thenDeaths(self: *const CondBr) []*Inst { - return self.deaths[0..self.then_death_count]; - } - pub fn elseDeaths(self: *const CondBr) []*Inst { - return (self.deaths + self.then_death_count)[0..self.else_death_count]; - } - }; - - pub const Constant = struct { - pub const base_tag = Tag.constant; - - base: Inst, - val: Value, - - pub fn operandCount(self: *const Constant) usize { - _ = self; - return 0; - } - pub fn getOperand(self: *const Constant, index: usize) ?*Inst { - _ = self; - _ = index; - return null; - } - }; - - pub const Loop = struct { - pub const base_tag = Tag.loop; - - base: Inst, - body: Body, - - pub fn operandCount(self: *const Loop) usize { - _ = self; - return 0; - } - pub fn getOperand(self: *const Loop, index: usize) ?*Inst { - _ = self; - _ = index; - return null; - } - }; - - pub const VarPtr = struct { - pub const base_tag = Tag.varptr; - - base: Inst, - variable: *Module.Var, - - pub fn operandCount(self: *const VarPtr) usize { - _ = self; - return 0; - } - pub fn getOperand(self: *const VarPtr, index: usize) ?*Inst { - _ = self; - _ = index; - return null; - } - }; - - pub const StructFieldPtr = struct { - pub const base_tag = Tag.struct_field_ptr; - - base: Inst, - struct_ptr: *Inst, - field_index: usize, - - pub fn operandCount(self: *const StructFieldPtr) usize { - _ = self; - return 1; - } - pub fn getOperand(self: *const StructFieldPtr, index: usize) ?*Inst { - _ = self; - _ = index; - var i = index; - - if (i < 1) - return self.struct_ptr; - i -= 1; - - return null; - } - }; - - pub const SwitchBr = struct { - pub const base_tag = Tag.switchbr; - - base: Inst, - target: *Inst, - cases: []Case, - /// Set of instructions whose lifetimes end at the start of one of the cases. - /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... ]. - deaths: [*]*Inst = undefined, - else_index: u32 = 0, - else_deaths: u32 = 0, - else_body: Body, - - pub const Case = struct { - item: Value, - body: Body, - index: u32 = 0, - deaths: u32 = 0, - }; - - pub fn operandCount(self: *const SwitchBr) usize { - _ = self; - return 1; - } - pub fn getOperand(self: *const SwitchBr, index: usize) ?*Inst { - var i = index; - - if (i < 1) - return self.target; - i -= 1; - - return null; - } - pub fn caseDeaths(self: *const SwitchBr, case_index: usize) []*Inst { - const case = self.cases[case_index]; - return (self.deaths + case.index)[0..case.deaths]; - } - pub fn elseDeaths(self: *const SwitchBr) []*Inst { - return (self.deaths + self.else_index)[0..self.else_deaths]; - } - }; - - pub const DbgStmt = struct { - pub const base_tag = Tag.dbg_stmt; - - base: Inst, - line: u32, - column: u32, - - pub fn operandCount(self: *const DbgStmt) usize { - _ = self; - return 0; - } - pub fn getOperand(self: *const DbgStmt, index: usize) ?*Inst { - _ = self; - _ = index; - return null; - } - }; -}; - -pub const Body = struct { - instructions: []*Inst, -}; - -/// For debugging purposes, prints a function representation to stderr. -pub fn dumpFn(old_module: Module, module_fn: *Module.Fn) void { - const allocator = old_module.gpa; - var ctx: DumpAir = .{ - .allocator = allocator, - .arena = std.heap.ArenaAllocator.init(allocator), - .old_module = &old_module, - .module_fn = module_fn, - .indent = 2, - .inst_table = DumpAir.InstTable.init(allocator), - .partial_inst_table = DumpAir.InstTable.init(allocator), - .const_table = DumpAir.InstTable.init(allocator), - }; - defer ctx.inst_table.deinit(); - defer ctx.partial_inst_table.deinit(); - defer ctx.const_table.deinit(); - defer ctx.arena.deinit(); - - switch (module_fn.state) { - .queued => std.debug.print("(queued)", .{}), - .inline_only => std.debug.print("(inline_only)", .{}), - .in_progress => std.debug.print("(in_progress)", .{}), - .sema_failure => std.debug.print("(sema_failure)", .{}), - .dependency_failure => std.debug.print("(dependency_failure)", .{}), - .success => { - const writer = std.io.getStdErr().writer(); - ctx.dump(module_fn.body, writer) catch @panic("failed to dump AIR"); - }, - } -} - -const DumpAir = struct { - allocator: *std.mem.Allocator, - arena: std.heap.ArenaAllocator, - old_module: *const Module, - module_fn: *Module.Fn, - indent: usize, - inst_table: InstTable, - partial_inst_table: InstTable, - const_table: InstTable, - next_index: usize = 0, - next_partial_index: usize = 0, - next_const_index: usize = 0, - - const InstTable = std.AutoArrayHashMap(*Inst, usize); - - /// TODO: Improve this code to include a stack of Body and store the instructions - /// in there. Now we are putting all the instructions in a function local table, - /// however instructions that are in a Body can be thown away when the Body ends. - fn dump(dtz: *DumpAir, body: Body, writer: std.fs.File.Writer) !void { - // First pass to pre-populate the table so that we can show even invalid references. - // Must iterate the same order we iterate the second time. - // We also look for constants and put them in the const_table. - try dtz.fetchInstsAndResolveConsts(body); - - std.debug.print("Module.Function(name={s}):\n", .{dtz.module_fn.owner_decl.name}); - - var it = dtz.const_table.iterator(); - while (it.next()) |entry| { - const constant = entry.key_ptr.*.castTag(.constant).?; - try writer.print(" @{d}: {} = {};\n", .{ - entry.value_ptr.*, constant.base.ty, constant.val, - }); - } - - return dtz.dumpBody(body, writer); - } - - fn fetchInstsAndResolveConsts(dtz: *DumpAir, body: Body) error{OutOfMemory}!void { - for (body.instructions) |inst| { - try dtz.inst_table.put(inst, dtz.next_index); - dtz.next_index += 1; - switch (inst.tag) { - .alloc, - .retvoid, - .unreach, - .breakpoint, - .dbg_stmt, - .arg, - => {}, - - .ref, - .ret, - .bitcast, - .not, - .is_non_null, - .is_non_null_ptr, - .is_null, - .is_null_ptr, - .is_err, - .is_non_err, - .is_err_ptr, - .is_non_err_ptr, - .ptrtoint, - .floatcast, - .intcast, - .load, - .optional_payload, - .optional_payload_ptr, - .wrap_optional, - .wrap_errunion_payload, - .wrap_errunion_err, - .unwrap_errunion_payload, - .unwrap_errunion_err, - .unwrap_errunion_payload_ptr, - .unwrap_errunion_err_ptr, - => { - const un_op = inst.cast(Inst.UnOp).?; - try dtz.findConst(un_op.operand); - }, - - .add, - .addwrap, - .sub, - .subwrap, - .mul, - .mulwrap, - .div, - .cmp_lt, - .cmp_lte, - .cmp_eq, - .cmp_gte, - .cmp_gt, - .cmp_neq, - .store, - .bool_and, - .bool_or, - .bit_and, - .bit_or, - .xor, - => { - const bin_op = inst.cast(Inst.BinOp).?; - try dtz.findConst(bin_op.lhs); - try dtz.findConst(bin_op.rhs); - }, - - .br => { - const br = inst.castTag(.br).?; - try dtz.findConst(&br.block.base); - try dtz.findConst(br.operand); - }, - - .br_block_flat => { - const br_block_flat = inst.castTag(.br_block_flat).?; - try dtz.findConst(&br_block_flat.block.base); - try dtz.fetchInstsAndResolveConsts(br_block_flat.body); - }, - - .br_void => { - const br_void = inst.castTag(.br_void).?; - try dtz.findConst(&br_void.block.base); - }, - - .block => { - const block = inst.castTag(.block).?; - try dtz.fetchInstsAndResolveConsts(block.body); - }, - - .condbr => { - const condbr = inst.castTag(.condbr).?; - try dtz.findConst(condbr.condition); - try dtz.fetchInstsAndResolveConsts(condbr.then_body); - try dtz.fetchInstsAndResolveConsts(condbr.else_body); - }, - .switchbr => { - const switchbr = inst.castTag(.switchbr).?; - try dtz.findConst(switchbr.target); - try dtz.fetchInstsAndResolveConsts(switchbr.else_body); - for (switchbr.cases) |case| { - try dtz.fetchInstsAndResolveConsts(case.body); - } - }, - - .loop => { - const loop = inst.castTag(.loop).?; - try dtz.fetchInstsAndResolveConsts(loop.body); - }, - .call => { - const call = inst.castTag(.call).?; - try dtz.findConst(call.func); - for (call.args) |arg| { - try dtz.findConst(arg); - } - }, - .struct_field_ptr => { - const struct_field_ptr = inst.castTag(.struct_field_ptr).?; - try dtz.findConst(struct_field_ptr.struct_ptr); - }, - - // TODO fill out this debug printing - .assembly, - .constant, - .varptr, - => {}, - } - } - } - - fn dumpBody(dtz: *DumpAir, body: Body, writer: std.fs.File.Writer) (std.fs.File.WriteError || error{OutOfMemory})!void { - for (body.instructions) |inst| { - const my_index = dtz.next_partial_index; - try dtz.partial_inst_table.put(inst, my_index); - dtz.next_partial_index += 1; - - try writer.writeByteNTimes(' ', dtz.indent); - try writer.print("%{d}: {} = {s}(", .{ - my_index, inst.ty, @tagName(inst.tag), - }); - switch (inst.tag) { - .alloc, - .retvoid, - .unreach, - .breakpoint, - .dbg_stmt, - => try writer.writeAll(")\n"), - - .ref, - .ret, - .bitcast, - .not, - .is_non_null, - .is_non_null_ptr, - .is_null, - .is_null_ptr, - .is_err, - .is_err_ptr, - .is_non_err, - .is_non_err_ptr, - .ptrtoint, - .floatcast, - .intcast, - .load, - .optional_payload, - .optional_payload_ptr, - .wrap_optional, - .wrap_errunion_err, - .wrap_errunion_payload, - .unwrap_errunion_err, - .unwrap_errunion_payload, - .unwrap_errunion_payload_ptr, - .unwrap_errunion_err_ptr, - => { - const un_op = inst.cast(Inst.UnOp).?; - const kinky = try dtz.writeInst(writer, un_op.operand); - if (kinky != null) { - try writer.writeAll(") // Instruction does not dominate all uses!\n"); - } else { - try writer.writeAll(")\n"); - } - }, - - .add, - .addwrap, - .sub, - .subwrap, - .mul, - .mulwrap, - .div, - .cmp_lt, - .cmp_lte, - .cmp_eq, - .cmp_gte, - .cmp_gt, - .cmp_neq, - .store, - .bool_and, - .bool_or, - .bit_and, - .bit_or, - .xor, - => { - const bin_op = inst.cast(Inst.BinOp).?; - - const lhs_kinky = try dtz.writeInst(writer, bin_op.lhs); - try writer.writeAll(", "); - const rhs_kinky = try dtz.writeInst(writer, bin_op.rhs); - - if (lhs_kinky != null or rhs_kinky != null) { - try writer.writeAll(") // Instruction does not dominate all uses!"); - if (lhs_kinky) |lhs| { - try writer.print(" %{d}", .{lhs}); - } - if (rhs_kinky) |rhs| { - try writer.print(" %{d}", .{rhs}); - } - try writer.writeAll("\n"); - } else { - try writer.writeAll(")\n"); - } - }, - - .arg => { - const arg = inst.castTag(.arg).?; - try writer.print("{s})\n", .{arg.name}); - }, - - .br => { - const br = inst.castTag(.br).?; - - const lhs_kinky = try dtz.writeInst(writer, &br.block.base); - try writer.writeAll(", "); - const rhs_kinky = try dtz.writeInst(writer, br.operand); - - if (lhs_kinky != null or rhs_kinky != null) { - try writer.writeAll(") // Instruction does not dominate all uses!"); - if (lhs_kinky) |lhs| { - try writer.print(" %{d}", .{lhs}); - } - if (rhs_kinky) |rhs| { - try writer.print(" %{d}", .{rhs}); - } - try writer.writeAll("\n"); - } else { - try writer.writeAll(")\n"); - } - }, - - .br_block_flat => { - const br_block_flat = inst.castTag(.br_block_flat).?; - const block_kinky = try dtz.writeInst(writer, &br_block_flat.block.base); - if (block_kinky != null) { - try writer.writeAll(", { // Instruction does not dominate all uses!\n"); - } else { - try writer.writeAll(", {\n"); - } - - const old_indent = dtz.indent; - dtz.indent += 2; - try dtz.dumpBody(br_block_flat.body, writer); - dtz.indent = old_indent; - - try writer.writeByteNTimes(' ', dtz.indent); - try writer.writeAll("})\n"); - }, - - .br_void => { - const br_void = inst.castTag(.br_void).?; - const kinky = try dtz.writeInst(writer, &br_void.block.base); - if (kinky) |_| { - try writer.writeAll(") // Instruction does not dominate all uses!\n"); - } else { - try writer.writeAll(")\n"); - } - }, - - .block => { - const block = inst.castTag(.block).?; - - try writer.writeAll("{\n"); - - const old_indent = dtz.indent; - dtz.indent += 2; - try dtz.dumpBody(block.body, writer); - dtz.indent = old_indent; - - try writer.writeByteNTimes(' ', dtz.indent); - try writer.writeAll("})\n"); - }, - - .condbr => { - const condbr = inst.castTag(.condbr).?; - - const condition_kinky = try dtz.writeInst(writer, condbr.condition); - if (condition_kinky != null) { - try writer.writeAll(", { // Instruction does not dominate all uses!\n"); - } else { - try writer.writeAll(", {\n"); - } - - const old_indent = dtz.indent; - dtz.indent += 2; - try dtz.dumpBody(condbr.then_body, writer); - - try writer.writeByteNTimes(' ', old_indent); - try writer.writeAll("}, {\n"); - - try dtz.dumpBody(condbr.else_body, writer); - dtz.indent = old_indent; - - try writer.writeByteNTimes(' ', old_indent); - try writer.writeAll("})\n"); - }, - - .switchbr => { - const switchbr = inst.castTag(.switchbr).?; - - const condition_kinky = try dtz.writeInst(writer, switchbr.target); - if (condition_kinky != null) { - try writer.writeAll(", { // Instruction does not dominate all uses!\n"); - } else { - try writer.writeAll(", {\n"); - } - const old_indent = dtz.indent; - - if (switchbr.else_body.instructions.len != 0) { - dtz.indent += 2; - try dtz.dumpBody(switchbr.else_body, writer); - - try writer.writeByteNTimes(' ', old_indent); - try writer.writeAll("}, {\n"); - dtz.indent = old_indent; - } - for (switchbr.cases) |case| { - dtz.indent += 2; - try dtz.dumpBody(case.body, writer); - - try writer.writeByteNTimes(' ', old_indent); - try writer.writeAll("}, {\n"); - dtz.indent = old_indent; - } - - try writer.writeByteNTimes(' ', old_indent); - try writer.writeAll("})\n"); - }, - - .loop => { - const loop = inst.castTag(.loop).?; - - try writer.writeAll("{\n"); - - const old_indent = dtz.indent; - dtz.indent += 2; - try dtz.dumpBody(loop.body, writer); - dtz.indent = old_indent; - - try writer.writeByteNTimes(' ', dtz.indent); - try writer.writeAll("})\n"); - }, - - .call => { - const call = inst.castTag(.call).?; - - const args_kinky = try dtz.allocator.alloc(?usize, call.args.len); - defer dtz.allocator.free(args_kinky); - std.mem.set(?usize, args_kinky, null); - var any_kinky_args = false; - - const func_kinky = try dtz.writeInst(writer, call.func); - - for (call.args) |arg, i| { - try writer.writeAll(", "); - - args_kinky[i] = try dtz.writeInst(writer, arg); - any_kinky_args = any_kinky_args or args_kinky[i] != null; - } - - if (func_kinky != null or any_kinky_args) { - try writer.writeAll(") // Instruction does not dominate all uses!"); - if (func_kinky) |func_index| { - try writer.print(" %{d}", .{func_index}); - } - for (args_kinky) |arg_kinky| { - if (arg_kinky) |arg_index| { - try writer.print(" %{d}", .{arg_index}); - } - } - try writer.writeAll("\n"); - } else { - try writer.writeAll(")\n"); - } - }, - - .struct_field_ptr => { - const struct_field_ptr = inst.castTag(.struct_field_ptr).?; - const kinky = try dtz.writeInst(writer, struct_field_ptr.struct_ptr); - if (kinky != null) { - try writer.print("{d}) // Instruction does not dominate all uses!\n", .{ - struct_field_ptr.field_index, - }); - } else { - try writer.print("{d})\n", .{struct_field_ptr.field_index}); - } - }, - - // TODO fill out this debug printing - .assembly, - .constant, - .varptr, - => { - try writer.writeAll("!TODO!)\n"); - }, - } - } - } - - fn writeInst(dtz: *DumpAir, writer: std.fs.File.Writer, inst: *Inst) !?usize { - if (dtz.partial_inst_table.get(inst)) |operand_index| { - try writer.print("%{d}", .{operand_index}); - return null; - } else if (dtz.const_table.get(inst)) |operand_index| { - try writer.print("@{d}", .{operand_index}); - return null; - } else if (dtz.inst_table.get(inst)) |operand_index| { - try writer.print("%{d}", .{operand_index}); - return operand_index; - } else { - try writer.writeAll("!BADREF!"); - return null; - } - } - - fn findConst(dtz: *DumpAir, operand: *Inst) !void { - if (operand.tag == .constant) { - try dtz.const_table.put(operand, dtz.next_const_index); - dtz.next_const_index += 1; - } - } -}; diff --git a/src/codegen.zig b/src/codegen.zig index 6050fe0ed8574d50bea105884cf487c413a1661e..205bab755a4fbf43fe4729b3f88acff45caca3bb 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2,7 +2,7 @@ const std = @import("std"); const mem = std.mem; const math = std.math; const assert = std.debug.assert; -const ir = @import("air.zig"); +const Air = @import("Air.zig"); const Type = @import("type.zig").Type; const Value = @import("value.zig").Value; const TypedValue = @import("TypedValue.zig"); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 391375c70966c81977d9710702a7e5c5c5c5051f..e3f2423746a4fe170add87f498d8fe6b8ad64f23 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -6,8 +6,7 @@ const log = std.log.scoped(.c); const link = @import("../link.zig"); const Module = @import("../Module.zig"); const Compilation = @import("../Compilation.zig"); -const ir = @import("../air.zig"); -const Inst = ir.Inst; +const Air = @import("../Air.zig"); const Value = @import("../value.zig").Value; const Type = @import("../type.zig").Type; const TypedValue = @import("../TypedValue.zig"); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b8f96891f4aa436879cfe1ea60e33ac9a6caa15f..45ee2d9bb85a6aa62c979fccb9193a4bfdc78fc4 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -9,7 +9,7 @@ const math = std.math; const Module = @import("../Module.zig"); const TypedValue = @import("../TypedValue.zig"); -const ir = @import("../air.zig"); +const Air = @import("../Air.zig"); const Inst = ir.Inst; const Value = @import("../value.zig").Value; diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 7fa813e56554634334463ae75cfb1ddee9a82c7b..60e9a962753238d89d8eded7efa35530fed53811 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -12,8 +12,7 @@ const Decl = Module.Decl; const Type = @import("../type.zig").Type; const Value = @import("../value.zig").Value; const LazySrcLoc = Module.LazySrcLoc; -const ir = @import("../air.zig"); -const Inst = ir.Inst; +const Air = @import("../Air.zig"); pub const Word = u32; pub const ResultId = u32; diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 3476ab2ce6b6d3d25ff5e89a68c2ca9977a3c76b..45b00ddfad8cdda1bf7d3080652fba500bd23365 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -9,8 +9,7 @@ const wasm = std.wasm; const Module = @import("../Module.zig"); const Decl = Module.Decl; -const ir = @import("../air.zig"); -const Inst = ir.Inst; +const Air = @import("../Air.zig"); const Type = @import("../type.zig").Type; const Value = @import("../value.zig").Value; const Compilation = @import("../Compilation.zig"); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index d754b478b9b3dd477848c893d6c3e7721f624679..90224866baabdee1c1a66ea4f6f9779faeab2ee4 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -10,7 +10,7 @@ const log = std.log.scoped(.link); const DW = std.dwarf; const leb128 = std.leb; -const ir = @import("../air.zig"); +const Air = @import("../Air.zig"); const Module = @import("../Module.zig"); const Compilation = @import("../Compilation.zig"); const codegen = @import("../codegen.zig"); diff --git a/src/liveness.zig b/src/liveness.zig index d115af77ed3d1f2a1c6fa265ec7a3cdf964b1fa8..e6692e4fc3a9cda8bbb2e51c76a6a972ac328d26 100644 --- a/src/liveness.zig +++ b/src/liveness.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const ir = @import("air.zig"); +const Air = @import("Air.zig"); const trace = @import("tracy.zig").trace; const log = std.log.scoped(.liveness); const assert = std.debug.assert; diff --git a/src/register_manager.zig b/src/register_manager.zig index 96cf4f17b7f38e8f3d5bc66d6181cfa12287899e..9c61423706ec36b62db5b2701e70c08d251655ee 100644 --- a/src/register_manager.zig +++ b/src/register_manager.zig @@ -3,7 +3,7 @@ const math = std.math; const mem = std.mem; const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const ir = @import("air.zig"); +const Air = @import("Air.zig"); const Type = @import("type.zig").Type; const Module = @import("Module.zig"); const LazySrcLoc = Module.LazySrcLoc; diff --git a/src/value.zig b/src/value.zig index b4cd63b8d3699c32d0385ecb42794889249fc3f9..48cd6fffc47686dc9b94015990c907cf3c027a03 100644 --- a/src/value.zig +++ b/src/value.zig @@ -7,7 +7,7 @@ const BigIntMutable = std.math.big.int.Mutable; const Target = std.Target; const Allocator = std.mem.Allocator; const Module = @import("Module.zig"); -const ir = @import("air.zig"); +const Air = @import("Air.zig"); /// This is the raw data, with no bookkeeping, no memory awareness, /// no de-duplication, and no type system awareness.