diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 2a81560945..426b28488f 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2151,7 +2151,8 @@ pub fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_sr }); } - return self.fail(scope, src, "TODO implement runtime deref", .{}); + const b = try self.requireRuntimeBlock(scope, src); + return self.addUnOp(b, src, elem_ty, .load, ptr); } pub fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name: []const u8) InnerError!*Inst { @@ -2504,6 +2505,22 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst return self.fail(scope, inst.src, "TODO implement type coercion from {} to {}", .{ inst.ty, dest_type }); } +pub fn storePtr(self: *Module, scope: *Scope, src: usize, ptr: *Inst, uncasted_value: *Inst) !*Inst { + if (ptr.ty.isConstPtr()) + return self.fail(scope, src, "cannot assign to constant", .{}); + + const elem_ty = ptr.ty.elemType(); + const value = try self.coerce(scope, elem_ty, uncasted_value); + if (elem_ty.onePossibleValue()) + return self.constVoid(scope, src); + + // TODO handle comptime pointer writes + // TODO handle if the element type requires comptime + + const b = try self.requireRuntimeBlock(scope, src); + return self.addBinOp(b, src, Type.initTag(.void), .store, ptr, value); +} + pub fn bitcast(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { if (inst.value()) |val| { // Keep the comptime Value representation; take the new type. @@ -2780,3 +2797,9 @@ pub fn singleMutPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type) type_payload.* = .{ .pointee_type = elem_ty }; return Type.initPayload(&type_payload.base); } + +pub fn singleConstPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type) error{OutOfMemory}!Type { + const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer); + type_payload.* = .{ .pointee_type = elem_ty }; + return Type.initPayload(&type_payload.base); +} diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index eeeb74a0d8..6f369e7879 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -87,7 +87,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .ArrayCat => return simpleBinOp(mod, scope, rl, node.castTag(.ArrayCat).?, .array_cat), .ArrayMult => return simpleBinOp(mod, scope, rl, node.castTag(.ArrayMult).?, .array_mul), - .Identifier => return rlWrap(mod, scope, rl, try identifier(mod, scope, node.castTag(.Identifier).?)), + .Identifier => return try identifier(mod, scope, rl, node.castTag(.Identifier).?), .Asm => return rlWrap(mod, scope, rl, try assembly(mod, scope, node.castTag(.Asm).?)), .StringLiteral => return rlWrap(mod, scope, rl, try stringLiteral(mod, scope, node.castTag(.StringLiteral).?)), .IntegerLiteral => return rlWrap(mod, scope, rl, try integerLiteral(mod, scope, node.castTag(.IntegerLiteral).?)), @@ -469,7 +469,7 @@ fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerE } } -fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError!*zir.Inst { +fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneToken) InnerError!*zir.Inst { const tracy = trace(@src()); defer tracy.end(); @@ -481,7 +481,8 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError } if (getSimplePrimitiveValue(ident_name)) |typed_value| { - return addZIRInstConst(mod, scope, src, typed_value); + const result = try addZIRInstConst(mod, scope, src, typed_value); + return rlWrap(mod, scope, rl, result); } if (ident_name.len >= 2) integer: { @@ -505,16 +506,18 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError else => { const int_type_payload = try scope.arena().create(Value.Payload.IntType); int_type_payload.* = .{ .signed = is_signed, .bits = bit_count }; - return addZIRInstConst(mod, scope, src, .{ + const result = try addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.comptime_int), .val = Value.initPayload(&int_type_payload.base), }); + return rlWrap(mod, scope, rl, result); }, }; - return addZIRInstConst(mod, scope, src, .{ + const result = try addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.type), .val = val, }); + return rlWrap(mod, scope, rl, result); } } @@ -525,14 +528,19 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError .local_val => { const local_val = s.cast(Scope.LocalVal).?; if (mem.eql(u8, local_val.name, ident_name)) { - return local_val.inst; + return rlWrap(mod, scope, rl, local_val.inst); } s = local_val.parent; }, .local_ptr => { const local_ptr = s.cast(Scope.LocalPtr).?; if (mem.eql(u8, local_ptr.name, ident_name)) { - return try addZIRUnOp(mod, scope, src, .deref, local_ptr.ptr); + if (rl == .lvalue) { + return local_ptr.ptr; + } else { + const result = try addZIRUnOp(mod, scope, src, .deref, local_ptr.ptr); + return rlWrap(mod, scope, rl, result); + } } s = local_ptr.parent; }, @@ -542,7 +550,9 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError } if (mod.lookupDeclName(scope, ident_name)) |decl| { - return try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); + // TODO handle lvalues + const result = try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); + return rlWrap(mod, scope, rl, result); } return mod.failNode(scope, &ident.base, "use of undeclared identifier '{}'", .{ident_name}); @@ -1066,10 +1076,7 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr .ptr = ptr_inst, .value = result, }, .{}); - _ = try addZIRInst(mod, scope, result.src, zir.Inst.Store, .{ - .ptr = ptr_inst, - .value = casted_result, - }, .{}); + _ = try addZIRBinOp(mod, scope, result.src, .store, ptr_inst, casted_result); return casted_result; }, .bitcasted_ptr => |bitcasted_ptr| { diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 75b042308d..777c9ee5f2 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -209,6 +209,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { err_msg: ?*ErrorMsg, args: []MCValue, ret_mcv: MCValue, + fn_type: Type, arg_index: usize, src: usize, stack_align: u32, @@ -230,15 +231,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { /// No more references to this value remain. dead, /// A pointer-sized integer that fits in a register. + /// If the type is a pointer, this is the pointer address in virtual address space. immediate: u64, /// The constant was emitted into the code, at this offset. + /// If the type is a pointer, it means the pointer address is embedded in the code. embedded_in_code: usize, + /// The value is a pointer to a constant which was emitted into the code, at this offset. + ptr_embedded_in_code: usize, /// The value is in a target-specific register. register: Register, /// The value is in memory at a hard-coded address. + /// If the type is a pointer, it means the pointer address is at this memory location. memory: u64, /// The value is one of the stack variables. - stack_offset: u64, + /// If the type is a pointer, it means the pointer address is in the stack at this offset. + stack_offset: u32, + /// The value is a pointer to one of the stack variables (payload is stack offset). + ptr_stack_offset: u32, /// The value is in the compare flags assuming an unsigned operation, /// with this operator applied on top of it. compare_flags_unsigned: math.CompareOperator, @@ -271,6 +280,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .memory, .compare_flags_unsigned, .compare_flags_signed, + .ptr_stack_offset, + .ptr_embedded_in_code, => false, .register, @@ -356,6 +367,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .err_msg = null, .args = undefined, // populated after `resolveCallingConventionValues` .ret_mcv = undefined, // populated after `resolveCallingConventionValues` + .fn_type = fn_type, .arg_index = 0, .branch_stack = &branch_stack, .src = src, @@ -459,26 +471,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .cmp_neq => return self.genCmp(inst.castTag(.cmp_neq).?, .neq), .condbr => return self.genCondBr(inst.castTag(.condbr).?), .constant => unreachable, // excluded from function bodies - .isnonnull => return self.genIsNonNull(inst.castTag(.isnonnull).?), - .isnull => return self.genIsNull(inst.castTag(.isnull).?), - .ptrtoint => return self.genPtrToInt(inst.castTag(.ptrtoint).?), - .ret => return self.genRet(inst.castTag(.ret).?), - .retvoid => return self.genRetVoid(inst.castTag(.retvoid).?), - .sub => return self.genSub(inst.castTag(.sub).?), - .unreach => return MCValue{ .unreach = {} }, - .not => return self.genNot(inst.castTag(.not).?), .floatcast => return self.genFloatCast(inst.castTag(.floatcast).?), .intcast => return self.genIntCast(inst.castTag(.intcast).?), + .isnonnull => return self.genIsNonNull(inst.castTag(.isnonnull).?), + .isnull => return self.genIsNull(inst.castTag(.isnull).?), + .load => return self.genLoad(inst.castTag(.load).?), + .not => return self.genNot(inst.castTag(.not).?), + .ptrtoint => return self.genPtrToInt(inst.castTag(.ptrtoint).?), + .ref => return self.genRef(inst.castTag(.ref).?), + .ret => return self.genRet(inst.castTag(.ret).?), + .retvoid => return self.genRetVoid(inst.castTag(.retvoid).?), + .store => return self.genStore(inst.castTag(.store).?), + .sub => return self.genSub(inst.castTag(.sub).?), + .unreach => return MCValue{ .unreach = {} }, } } - fn genAlloc(self: *Self, inst: *ir.Inst.NoOp) !MCValue { - const elem_ty = inst.base.ty.elemType(); - const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { - return self.fail(inst.base.src, "type '{}' too big to fit into stack frame", .{elem_ty}); - }; - // TODO swap this for inst.base.ty.ptrAlign - const abi_align = elem_ty.abiAlignment(self.target.*); + fn allocMem(self: *Self, inst: *ir.Inst, abi_size: u32, abi_align: u32) !u32 { if (abi_align > self.stack_align) self.stack_align = abi_align; const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; @@ -488,10 +497,66 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (branch.next_stack_offset > branch.max_end_stack) branch.max_end_stack = branch.next_stack_offset; try branch.stack.putNoClobber(self.gpa, offset, .{ - .inst = &inst.base, + .inst = inst, .size = abi_size, }); - return MCValue{ .stack_offset = offset }; + return offset; + } + + /// Use a pointer instruction as the basis for allocating stack memory. + fn allocMemPtr(self: *Self, inst: *ir.Inst) !u32 { + const elem_ty = inst.ty.elemType(); + const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { + return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty}); + }; + // TODO swap this for inst.ty.ptrAlign + const abi_align = elem_ty.abiAlignment(self.target.*); + return self.allocMem(inst, abi_size, abi_align); + } + + fn allocRegOrMem(self: *Self, inst: *ir.Inst) !MCValue { + const elem_ty = inst.ty; + const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { + return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty}); + }; + const abi_align = elem_ty.abiAlignment(self.target.*); + if (abi_align > self.stack_align) + self.stack_align = abi_align; + const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; + + // TODO Make sure the type can fit in a register before we try to allocate one. + const free_index = @ctz(FreeRegInt, branch.free_registers); + if (free_index >= callee_preserved_regs.len) { + const stack_offset = try self.allocMem(inst, abi_size, abi_align); + return MCValue{ .stack_offset = stack_offset }; + } + branch.free_registers &= ~(@as(FreeRegInt, 1) << free_index); + const reg = callee_preserved_regs[free_index]; + try branch.registers.putNoClobber(self.gpa, reg, .{ .inst = inst }); + return MCValue{ .register = reg }; + } + + /// Does not "move" the instruction. + fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue { + const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; + try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); + try branch.inst_table.ensureCapacity(self.gpa, branch.inst_table.items().len + 1); + + const free_index = @ctz(FreeRegInt, branch.free_registers); + if (free_index >= callee_preserved_regs.len) + return self.fail(inst.src, "TODO implement spilling register to stack", .{}); + branch.free_registers &= ~(@as(FreeRegInt, 1) << free_index); + const reg = callee_preserved_regs[free_index]; + branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); + const old_mcv = branch.inst_table.get(inst).?; + const new_mcv: MCValue = .{ .register = reg }; + try self.genSetReg(inst.src, reg, old_mcv); + return new_mcv; + } + + fn genAlloc(self: *Self, inst: *ir.Inst.NoOp) !MCValue { + const stack_offset = try self.allocMemPtr(&inst.base); + return MCValue{ .ptr_stack_offset = stack_offset }; } fn genFloatCast(self: *Self, inst: *ir.Inst.UnOp) !MCValue { @@ -572,6 +637,85 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } + fn genLoad(self: *Self, inst: *ir.Inst.UnOp) !MCValue { + const elem_ty = inst.base.ty; + if (!elem_ty.hasCodeGenBits()) + return MCValue.none; + const ptr = try self.resolveInst(inst.operand); + const is_volatile = inst.operand.ty.isVolatilePtr(); + if (inst.base.isUnused() and !is_volatile) + return MCValue.dead; + const dst_mcv: MCValue = blk: { + if (inst.base.operandDies(0) and ptr.isMutable()) { + // The MCValue that holds the pointer can be re-used as the value. + // TODO track this in the register/stack allocation metadata. + break :blk ptr; + } else { + break :blk try self.allocRegOrMem(&inst.base); + } + }; + switch (ptr) { + .none => unreachable, + .unreach => unreachable, + .dead => unreachable, + .compare_flags_unsigned => unreachable, + .compare_flags_signed => unreachable, + .immediate => |imm| try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .memory = imm }), + .ptr_stack_offset => |off| try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .stack_offset = off }), + .ptr_embedded_in_code => |off| { + try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .embedded_in_code = off }); + }, + .embedded_in_code => { + return self.fail(inst.base.src, "TODO implement loading from MCValue.embedded_in_code", .{}); + }, + .register => { + return self.fail(inst.base.src, "TODO implement loading from MCValue.register", .{}); + }, + .memory => { + return self.fail(inst.base.src, "TODO implement loading from MCValue.memory", .{}); + }, + .stack_offset => { + return self.fail(inst.base.src, "TODO implement loading from MCValue.stack_offset", .{}); + }, + } + return dst_mcv; + } + + fn genStore(self: *Self, inst: *ir.Inst.BinOp) !MCValue { + const ptr = try self.resolveInst(inst.lhs); + const value = try self.resolveInst(inst.rhs); + const elem_ty = inst.rhs.ty; + switch (ptr) { + .none => unreachable, + .unreach => unreachable, + .dead => unreachable, + .compare_flags_unsigned => unreachable, + .compare_flags_signed => unreachable, + .immediate => |imm| { + try self.setRegOrMem(inst.base.src, elem_ty, .{ .memory = imm }, value); + }, + .ptr_stack_offset => |off| { + try self.genSetStack(inst.base.src, elem_ty, off, value); + }, + .ptr_embedded_in_code => |off| { + try self.setRegOrMem(inst.base.src, elem_ty, .{ .embedded_in_code = off }, value); + }, + .embedded_in_code => { + return self.fail(inst.base.src, "TODO implement storing to MCValue.embedded_in_code", .{}); + }, + .register => { + return self.fail(inst.base.src, "TODO implement storing to MCValue.register", .{}); + }, + .memory => { + return self.fail(inst.base.src, "TODO implement storing to MCValue.memory", .{}); + }, + .stack_offset => { + return self.fail(inst.base.src, "TODO implement storing to MCValue.stack_offset", .{}); + }, + } + return .none; + } + fn genSub(self: *Self, inst: *ir.Inst.BinOp) !MCValue { // No side effects, so if it's unreferenced, do nothing. if (inst.base.isUnused()) @@ -657,10 +801,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .dead, .unreach, .immediate => unreachable, .compare_flags_unsigned => unreachable, .compare_flags_signed => unreachable, + .ptr_stack_offset => unreachable, + .ptr_embedded_in_code => unreachable, .register => |dst_reg| { switch (src_mcv) { .none => unreachable, .dead, .unreach => unreachable, + .ptr_stack_offset => unreachable, + .ptr_embedded_in_code => unreachable, .register => |src_reg| { self.rex(.{ .b = dst_reg.isExtended(), .r = src_reg.isExtended(), .w = dst_reg.size() == 64 }); self.code.appendSliceAssumeCapacity(&[_]u8{ mr + 0x1, 0xC0 | (@as(u8, src_reg.id() & 0b111) << 3) | @as(u8, dst_reg.id() & 0b111) }); @@ -743,6 +891,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { for (info.args) |mc_arg, arg_i| { const arg = inst.args[arg_i]; const arg_mcv = try self.resolveInst(inst.args[arg_i]); + // Here we do not use setRegOrMem even though the logic is similar, because + // the function call will move the stack pointer, so the offsets are different. switch (mc_arg) { .none => continue, .register => |reg| { @@ -754,6 +904,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // mov qword ptr [rsp + stack_offset], x return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); }, + .ptr_stack_offset => { + return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset", .{}); + }, + .ptr_embedded_in_code => { + return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code", .{}); + }, .immediate => unreachable, .unreach => unreachable, .dead => unreachable, @@ -788,8 +944,34 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return info.return_value; } + fn genRef(self: *Self, inst: *ir.Inst.UnOp) !MCValue { + const operand = try self.resolveInst(inst.operand); + switch (operand) { + .unreach => unreachable, + .dead => unreachable, + .none => return .none, + + .immediate, + .register, + .ptr_stack_offset, + .ptr_embedded_in_code, + .compare_flags_unsigned, + .compare_flags_signed, + => { + const stack_offset = try self.allocMemPtr(&inst.base); + try self.genSetStack(inst.base.src, inst.operand.ty, stack_offset, operand); + return MCValue{ .ptr_stack_offset = stack_offset }; + }, + + .stack_offset => |offset| return MCValue{ .ptr_stack_offset = offset }, + .embedded_in_code => |offset| return MCValue{ .ptr_embedded_in_code = offset }, + .memory => |vaddr| return MCValue{ .immediate = vaddr }, + } + } + fn ret(self: *Self, src: usize, mcv: MCValue) !MCValue { - try self.setRegOrStack(src, self.ret_mcv, mcv); + const ret_ty = self.fn_type.fnReturnType(); + try self.setRegOrMem(src, ret_ty, self.ret_mcv, mcv); switch (arch) { .i386 => { try self.code.append(0xc3); // ret @@ -1042,21 +1224,74 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } /// Sets the value without any modifications to register allocation metadata or stack allocation metadata. - fn setRegOrStack(self: *Self, src: usize, loc: MCValue, val: MCValue) !void { + fn setRegOrMem(self: *Self, src: usize, ty: Type, loc: MCValue, val: MCValue) !void { switch (loc) { .none => return, .register => |reg| return self.genSetReg(src, reg, val), - .stack_offset => { - return self.fail(src, "TODO implement setRegOrStack for stack offset", .{}); + .stack_offset => |off| return self.genSetStack(src, ty, off, val), + .memory => { + return self.fail(src, "TODO implement setRegOrMem for memory", .{}); }, else => unreachable, } } - fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) error{ CodegenFail, OutOfMemory }!void { + fn genSetStack(self: *Self, src: usize, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void { switch (arch) { .x86_64 => switch (mcv) { .dead => unreachable, + .ptr_stack_offset => unreachable, + .ptr_embedded_in_code => unreachable, + .unreach, .none => return, // Nothing to do. + .compare_flags_unsigned => |op| { + return self.fail(src, "TODO implement set stack variable with compare flags value (unsigned)", .{}); + }, + .compare_flags_signed => |op| { + return self.fail(src, "TODO implement set stack variable with compare flags value (signed)", .{}); + }, + .immediate => |x_big| { + try self.code.ensureCapacity(self.code.items.len + 7); + if (x_big <= math.maxInt(u32)) { + const x = @intCast(u32, x_big); + if (stack_offset > 128) { + return self.fail(src, "TODO implement set stack variable with large stack offset", .{}); + } + // We have a positive stack offset value but we want a twos complement negative + // offset from rbp, which is at the top of the stack frame. + const negative_offset = @intCast(i8, -@intCast(i32, stack_offset)); + const twos_comp = @bitCast(u8, negative_offset); + // mov DWORD PTR [rbp+offset], immediate + self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp }); + mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x); + } else { + return self.fail(src, "TODO implement set stack variable with large immediate", .{}); + } + }, + .embedded_in_code => |code_offset| { + return self.fail(src, "TODO implement set stack variable from embedded_in_code", .{}); + }, + .register => |reg| { + return self.fail(src, "TODO implement set stack variable from register", .{}); + }, + .memory => |vaddr| { + return self.fail(src, "TODO implement set stack variable from memory vaddr", .{}); + }, + .stack_offset => |off| { + if (stack_offset == off) + return; // Copy stack variable to itself; nothing to do. + return self.fail(src, "TODO implement copy stack variable to stack variable", .{}); + }, + }, + else => return self.fail(src, "TODO implement getSetStack for {}", .{self.target.cpu.arch}), + } + } + + fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) InnerError!void { + switch (arch) { + .x86_64 => switch (mcv) { + .dead => unreachable, + .ptr_stack_offset => unreachable, + .ptr_embedded_in_code => unreachable, .unreach, .none => return, // Nothing to do. .compare_flags_unsigned => |op| { try self.code.ensureCapacity(self.code.items.len + 3); @@ -1279,24 +1514,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - /// Does not "move" the instruction. - fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue { - const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; - try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); - try branch.inst_table.ensureCapacity(self.gpa, branch.inst_table.items().len + 1); - - const free_index = @ctz(FreeRegInt, branch.free_registers); - if (free_index >= callee_preserved_regs.len) - return self.fail(inst.src, "TODO implement spilling register to stack", .{}); - branch.free_registers &= ~(@as(FreeRegInt, 1) << free_index); - const reg = callee_preserved_regs[free_index]; - branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); - const old_mcv = branch.inst_table.get(inst).?; - const new_mcv: MCValue = .{ .register = reg }; - try self.genSetReg(inst.src, reg, old_mcv); - return new_mcv; - } - /// If the MCValue is an immediate, and it does not fit within this type, /// we put it in a register. /// A potential opportunity for future optimization here would be keeping track diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 9b281e1a9e..3965a2ea93 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -67,9 +67,14 @@ pub const Inst = struct { constant, isnonnull, isnull, + /// Read a value from a pointer. + load, ptrtoint, + ref, ret, retvoid, + /// Write a value to a pointer. LHS is pointer, RHS is value. + store, sub, unreach, not, @@ -85,6 +90,7 @@ pub const Inst = struct { .breakpoint, => NoOp, + .ref, .ret, .bitcast, .not, @@ -93,6 +99,7 @@ pub const Inst = struct { .ptrtoint, .floatcast, .intcast, + .load, => UnOp, .add, @@ -103,6 +110,7 @@ pub const Inst = struct { .cmp_gte, .cmp_gt, .cmp_neq, + .store, => BinOp, .assembly => Assembly, diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 7a9e3c9b8e..8f090cef04 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -803,6 +803,58 @@ pub const Type = extern union { }; } + pub fn isVolatilePtr(self: Type) bool { + return switch (self.tag()) { + .u8, + .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .f16, + .f32, + .f64, + .f128, + .c_void, + .bool, + .void, + .type, + .anyerror, + .comptime_int, + .comptime_float, + .noreturn, + .@"null", + .@"undefined", + .array, + .array_u8_sentinel_0, + .fn_noreturn_no_args, + .fn_void_no_args, + .fn_naked_noreturn_no_args, + .fn_ccc_void_no_args, + .function, + .int_unsigned, + .int_signed, + .single_mut_pointer, + .single_const_pointer, + .single_const_pointer_to_comptime_int, + .const_slice_u8, + => false, + }; + } + /// Asserts the type is a pointer or array type. pub fn elemType(self: Type) Type { return switch (self.tag()) { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 841f456f8f..6b98f1d493 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -242,6 +242,7 @@ pub const Inst = struct { .mulwrap, .shl, .shr, + .store, .sub, .subwrap, .cmp_lt, @@ -270,7 +271,6 @@ pub const Inst = struct { .coerce_result_block_ptr => CoerceResultBlockPtr, .compileerror => CompileError, .@"const" => Const, - .store => Store, .str => Str, .int => Int, .inttype => IntType, @@ -545,17 +545,6 @@ pub const Inst = struct { kw_args: struct {}, }; - pub const Store = struct { - pub const base_tag = Tag.store; - base: Inst, - - positionals: struct { - ptr: *Inst, - value: *Inst, - }, - kw_args: struct {}, - }; - pub const Str = struct { pub const base_tag = Tag.str; base: Inst, @@ -1837,9 +1826,12 @@ const EmitZIR = struct { .ptrtoint => try self.emitUnOp(inst.src, new_body, inst.castTag(.ptrtoint).?, .ptrtoint), .isnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnull).?, .isnull), .isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull), + .load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref), + .ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref), .add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add), .sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub), + .store => try self.emitBinOp(inst.src, new_body, inst.castTag(.store).?, .store), .cmp_lt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lt).?, .cmp_lt), .cmp_lte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lte).?, .cmp_lte), .cmp_eq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_eq).?, .cmp_eq), diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index dbbd00d333..45b49536e5 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -287,8 +287,11 @@ fn analyzeInstCoerceResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp return mod.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultPtr", .{}); } +/// Equivalent to `as(ptr_child_type(typeof(ptr)), value)`. fn analyzeInstCoerceToPtrElem(mod: *Module, scope: *Scope, inst: *zir.Inst.CoerceToPtrElem) InnerError!*Inst { - return mod.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceToPtrElem", .{}); + const ptr = try resolveInst(mod, scope, inst.positionals.ptr); + const operand = try resolveInst(mod, scope, inst.positionals.value); + return mod.coerce(scope, ptr.ty.elemType(), operand); } fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { @@ -296,7 +299,10 @@ fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerErr } fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { - return mod.fail(scope, inst.base.src, "TODO implement analyzeInstRef", .{}); + const operand = try resolveInst(mod, scope, inst.positionals.operand); + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + const ptr_type = try mod.singleConstPtrType(scope, inst.base.src, operand.ty); + return mod.addUnOp(b, inst.base.src, ptr_type, .ref, operand); } fn analyzeInstRetType(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { @@ -333,8 +339,10 @@ fn analyzeInstAllocInferred(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) I return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{}); } -fn analyzeInstStore(mod: *Module, scope: *Scope, inst: *zir.Inst.Store) InnerError!*Inst { - return mod.fail(scope, inst.base.src, "TODO implement analyzeInstStore", .{}); +fn analyzeInstStore(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const ptr = try resolveInst(mod, scope, inst.positionals.lhs); + const value = try resolveInst(mod, scope, inst.positionals.rhs); + return mod.storePtr(scope, inst.base.src, ptr, value); } fn analyzeInstParamType(mod: *Module, scope: *Scope, inst: *zir.Inst.ParamType) InnerError!*Inst {