stage2: more progress towards mutable local variables

* implement sema for runtime deref, store pointer, coerce_to_ptr_elem,
   and store
 * identifiers support being lvalues, except for decls is still TODO
 * codegen supports load, store, ref, alloc
 * introduce more MCValue union tags to support pointers
 * add load, ref, store typed IR instructions
 * add Type.isVolatilePtr
This commit is contained in:
Andrew Kelley 2020-07-28 17:27:44 -07:00
parent 11d38a7e52
commit 5ccee4c986
7 changed files with 377 additions and 70 deletions

View File

@ -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);
}

View File

@ -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| {

View File

@ -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

View File

@ -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,

View File

@ -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()) {

View File

@ -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),

View File

@ -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 {