Merge branch 'more-stage2-locals'

This commit is contained in:
Andrew Kelley 2020-07-29 02:29:46 -07:00
commit b3b00ec62f
12 changed files with 919 additions and 241 deletions

View File

@ -88,6 +88,8 @@ pub fn format(
if (args.len > ArgSetType.bit_count) {
@compileError("32 arguments max are supported per format call");
}
if (args.len == 0)
return writer.writeAll(fmt);
const State = enum {
Start,

View File

@ -47,7 +47,6 @@ export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{},
/// Maps fully qualified namespaced names to the Decl struct for them.
decl_table: std.HashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{},
optimize_mode: std.builtin.Mode,
link_error_flags: link.File.ErrorFlags = .{},
work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic),
@ -385,18 +384,6 @@ pub const Scope = struct {
};
}
pub fn dumpInst(self: *Scope, inst: *Inst) void {
const zir_module = self.namespace();
const loc = std.zig.findLineColumn(zir_module.source.bytes, inst.src);
std.debug.warn("{}:{}:{}: {}: ty={}\n", .{
zir_module.sub_file_path,
loc.line + 1,
loc.column + 1,
@tagName(inst.tag),
inst.ty,
});
}
/// Asserts the scope has a parent which is a ZIRModule or File and
/// returns the sub_file_path field.
pub fn subFilePath(base: *Scope) []const u8 {
@ -802,6 +789,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
.output_mode = options.output_mode,
.link_mode = options.link_mode orelse .Static,
.object_format = options.object_format orelse options.target.getObjectFormat(),
.optimize_mode = options.optimize_mode,
});
errdefer bin_file.destroy();
@ -838,7 +826,6 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
.bin_file_dir = bin_file_dir,
.bin_file_path = options.bin_file_path,
.bin_file = bin_file,
.optimize_mode = options.optimize_mode,
.work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa),
.keep_source_files_loaded = options.keep_source_files_loaded,
};
@ -894,7 +881,11 @@ fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
}
pub fn target(self: Module) std.Target {
return self.bin_file.options().target;
return self.bin_file.options.target;
}
pub fn optimizeMode(self: Module) std.builtin.Mode {
return self.bin_file.options.optimize_mode;
}
/// Detect changes to source files, perform semantic analysis, and update the output files.
@ -1991,14 +1982,14 @@ pub fn constType(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst {
pub fn constVoid(self: *Module, scope: *Scope, src: usize) !*Inst {
return self.constInst(scope, src, .{
.ty = Type.initTag(.void),
.val = Value.initTag(.the_one_possible_value),
.val = Value.initTag(.void_value),
});
}
pub fn constNoReturn(self: *Module, scope: *Scope, src: usize) !*Inst {
return self.constInst(scope, src, .{
.ty = Type.initTag(.noreturn),
.val = Value.initTag(.the_one_possible_value),
.val = Value.initTag(.unreachable_value),
});
}
@ -2151,7 +2142,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 {
@ -2161,7 +2153,8 @@ pub fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name:
}
pub fn wantSafety(self: *Module, scope: *Scope) bool {
return switch (self.optimize_mode) {
// TODO take into account scope's safety overrides
return switch (self.optimizeMode()) {
.Debug => true,
.ReleaseSafe => true,
.ReleaseFast => false,
@ -2504,6 +2497,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() != null)
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 +2789,41 @@ 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);
}
pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void {
const zir_module = scope.namespace();
const source = zir_module.getSource(self) catch @panic("dumpInst failed to get source");
const loc = std.zig.findLineColumn(source, inst.src);
if (inst.tag == .constant) {
std.debug.warn("constant ty={} val={} src={}:{}:{}\n", .{
inst.ty,
inst.castTag(.constant).?.val,
zir_module.subFilePath(),
loc.line + 1,
loc.column + 1,
});
} else if (inst.deaths == 0) {
std.debug.warn("{} ty={} src={}:{}:{}\n", .{
@tagName(inst.tag),
inst.ty,
zir_module.subFilePath(),
loc.line + 1,
loc.column + 1,
});
} else {
std.debug.warn("{} ty={} deaths={b} src={}:{}:{}\n", .{
@tagName(inst.tag),
inst.ty,
inst.deaths,
zir_module.subFilePath(),
loc.line + 1,
loc.column + 1,
});
}
}

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).?)),
@ -145,8 +145,10 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block
else => {
const possibly_unused_result = try expr(mod, scope, .none, statement);
const src = scope.tree().token_locs[statement.firstToken()].start;
_ = try addZIRUnOp(mod, scope, src, .ensure_result_used, possibly_unused_result);
if (!possibly_unused_result.tag.isNoReturn()) {
const src = scope.tree().token_locs[statement.firstToken()].start;
_ = try addZIRUnOp(mod, scope, src, .ensure_result_used, possibly_unused_result);
}
},
}
}
@ -469,7 +471,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 +483,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 +508,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 +530,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 +552,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 +1078,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

@ -50,7 +50,7 @@ pub fn generateSymbol(
switch (typed_value.ty.zigTypeTag()) {
.Fn => {
switch (bin_file.options.target.cpu.arch) {
switch (bin_file.base.options.target.cpu.arch) {
//.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code),
//.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code),
//.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code),
@ -143,7 +143,7 @@ pub fn generateSymbol(
// TODO handle the dependency of this symbol on the decl's vaddr.
// If the decl changes vaddr, then this symbol needs to get regenerated.
const vaddr = bin_file.local_symbols.items[decl.link.local_sym_index].st_value;
const endian = bin_file.options.target.cpu.arch.endian();
const endian = bin_file.base.options.target.cpu.arch.endian();
switch (bin_file.ptr_width) {
.p32 => {
try code.resize(4);
@ -166,7 +166,7 @@ pub fn generateSymbol(
};
},
.Int => {
const info = typed_value.ty.intInfo(bin_file.options.target);
const info = typed_value.ty.intInfo(bin_file.base.options.target);
if (info.bits == 8 and !info.signed) {
const x = typed_value.val.toUnsignedInt();
try code.append(@intCast(u8, x));
@ -209,10 +209,16 @@ 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,
/// The value is an offset into the `Function` `code` from the beginning.
/// To perform the reloc, write 32-bit signed little-endian integer
/// which is a relative jump, based on the address following the reloc.
exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{},
/// Whenever there is a runtime branch, we push a Branch onto this stack,
/// and pop it off when the runtime branch joins. This provides an "overlay"
/// of the table of mappings from instructions to `MCValue` from within the branch.
@ -229,16 +235,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
unreach,
/// No more references to this value remain.
dead,
/// The value is undefined.
undef,
/// 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 +287,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.memory,
.compare_flags_unsigned,
.compare_flags_signed,
.ptr_stack_offset,
.ptr_embedded_in_code,
.undef,
=> false,
.register,
@ -309,6 +328,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
self.free_registers |= @as(FreeRegInt, 1) << shift;
}
/// Before calling, must ensureCapacity + 1 on branch.registers.
/// Returns `null` if all registers are allocated.
fn allocReg(self: *Branch, inst: *ir.Inst) ?Register {
const free_index = @ctz(FreeRegInt, self.free_registers);
if (free_index >= callee_preserved_regs.len) {
return null;
}
self.free_registers &= ~(@as(FreeRegInt, 1) << free_index);
const reg = callee_preserved_regs[free_index];
self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst });
return reg;
}
fn deinit(self: *Branch, gpa: *Allocator) void {
self.inst_table.deinit(gpa);
self.registers.deinit(gpa);
@ -349,18 +381,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
var function = Self{
.gpa = bin_file.allocator,
.target = &bin_file.options.target,
.target = &bin_file.base.options.target,
.bin_file = bin_file,
.mod_fn = module_fn,
.code = code,
.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,
.stack_align = undefined,
};
defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
var call_info = function.resolveCallingConventionValues(src, fn_type) catch |err| switch (err) {
error.CodegenFail => return Result{ .fail = function.err_msg.? },
@ -386,29 +420,78 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn gen(self: *Self) !void {
try self.code.ensureCapacity(self.code.items.len + 11);
switch (arch) {
.x86_64 => {
try self.code.ensureCapacity(self.code.items.len + 11);
// TODO omit this for naked functions
// push rbp
// mov rbp, rsp
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x55, 0x48, 0x89, 0xe5 });
const cc = self.fn_type.fnCallingConvention();
if (cc != .Naked) {
// We want to subtract the aligned stack frame size from rsp here, but we don't
// yet know how big it will be, so we leave room for a 4-byte stack size.
// TODO During semantic analysis, check if there are no function calls. If there
// are none, here we can omit the part where we subtract and then add rsp.
self.code.appendSliceAssumeCapacity(&[_]u8{
// push rbp
0x55,
// mov rbp, rsp
0x48,
0x89,
0xe5,
// sub rsp, imm32 (with reloc)
0x48,
0x81,
0xec,
});
const reloc_index = self.code.items.len;
self.code.items.len += 4;
// sub rsp, x
const stack_end = self.branch_stack.items[0].max_end_stack;
if (stack_end > math.maxInt(i32)) {
return self.fail(self.src, "too much stack used in call parameters", .{});
} else if (stack_end > math.maxInt(i8)) {
// 48 83 ec xx sub rsp,0x10
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xec });
const x = @intCast(u32, stack_end);
mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);
} else if (stack_end != 0) {
// 48 81 ec xx xx xx xx sub rsp,0x80
const x = @intCast(u8, stack_end);
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xec, x });
try self.genBody(self.mod_fn.analysis.success);
const stack_end = self.branch_stack.items[0].max_end_stack;
if (stack_end > math.maxInt(i32))
return self.fail(self.src, "too much stack used in call parameters", .{});
const aligned_stack_end = mem.alignForward(stack_end, self.stack_align);
mem.writeIntLittle(u32, self.code.items[reloc_index..][0..4], @intCast(u32, aligned_stack_end));
if (self.code.items.len >= math.maxInt(i32)) {
return self.fail(self.src, "unable to perform relocation: jump too far", .{});
}
for (self.exitlude_jump_relocs.items) |jmp_reloc| {
const amt = self.code.items.len - (jmp_reloc + 4);
// If it wouldn't jump at all, elide it.
if (amt == 0) {
self.code.items.len -= 5;
continue;
}
const s32_amt = @intCast(i32, amt);
mem.writeIntLittle(i32, self.code.items[jmp_reloc..][0..4], s32_amt);
}
try self.code.ensureCapacity(self.code.items.len + 9);
// add rsp, x
if (aligned_stack_end > math.maxInt(i8)) {
// example: 48 81 c4 ff ff ff 7f add rsp,0x7fffffff
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xc4 });
const x = @intCast(u32, aligned_stack_end);
mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);
} else if (aligned_stack_end != 0) {
// example: 48 83 c4 7f add rsp,0x7f
const x = @intCast(u8, aligned_stack_end);
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xc4, x });
}
self.code.appendSliceAssumeCapacity(&[_]u8{
0x5d, // pop rbp
0xc3, // ret
});
} else {
try self.genBody(self.mod_fn.analysis.success);
}
},
else => {
try self.genBody(self.mod_fn.analysis.success);
},
}
try self.genBody(self.mod_fn.analysis.success);
}
fn genBody(self: *Self, body: ir.Body) InnerError!void {
@ -432,8 +515,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
entry.value = .dead;
switch (prev_value) {
.register => |reg| {
_ = branch.registers.remove(reg);
branch.markRegFree(reg);
const reg64 = reg.to64();
_ = branch.registers.remove(reg64);
branch.markRegFree(reg64);
},
else => {}, // TODO process stack allocation death
}
@ -459,26 +543,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 +569,62 @@ 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];
// Make sure the type can fit in a register before we try to allocate one.
const ptr_bits = arch.ptrBitWidth();
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
if (abi_size <= ptr_bytes) {
try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
if (branch.allocReg(inst)) |reg| {
return MCValue{ .register = registerAlias(reg, abi_size) };
}
}
const stack_offset = try self.allocMem(inst, abi_size, abi_align);
return MCValue{ .stack_offset = stack_offset };
}
/// 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);
const reg = branch.allocReg(inst) orelse
return self.fail(inst.src, "TODO implement spilling register to stack", .{});
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 +705,87 @@ 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,
.undef => 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,
.undef => 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())
@ -654,13 +868,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn genX8664BinMathCode(self: *Self, src: usize, dst_mcv: MCValue, src_mcv: MCValue, opx: u8, mr: u8) !void {
switch (dst_mcv) {
.none => unreachable,
.undef => unreachable,
.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,
.undef => try self.genSetReg(src, dst_reg, .undef),
.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 +963,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 +976,13 @@ 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 arg", .{});
},
.ptr_embedded_in_code => {
return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
},
.undef => unreachable,
.immediate => unreachable,
.unreach => unreachable,
.dead => unreachable,
@ -788,17 +1017,47 @@ 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 },
.undef => return self.fail(inst.base.src, "TODO implement ref on an undefined value", .{}),
}
}
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
},
.x86_64 => {
try self.code.appendSlice(&[_]u8{
0x5d, // pop rbp
0xc3, // ret
});
// TODO when implementing defer, this will need to jump to the appropriate defer expression.
// TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction
// which is available if the jump is 127 bytes or less forward.
try self.code.resize(self.code.items.len + 5);
self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32
try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4);
},
else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}),
}
@ -882,7 +1141,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// test reg, 1
// TODO detect al, ax, eax
try self.code.ensureCapacity(self.code.items.len + 4);
self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 });
// TODO audit this codegen: we force w = true here to make
// the value affect the big register
self.rex(.{ .b = reg.isExtended(), .w = true });
self.code.appendSliceAssumeCapacity(&[_]u8{
0xf6,
@as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()),
@ -938,6 +1199,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (reloc) {
.rel32 => |pos| {
const amt = self.code.items.len - (pos + 4);
// If it wouldn't jump at all, elide it.
if (amt == 0) {
self.code.items.len -= 5;
return;
}
const s32_amt = math.cast(i32, amt) catch
return self.fail(src, "unable to perform relocation: jump too far", .{});
mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt);
@ -1042,25 +1308,141 @@ 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.
.undef => {
if (!self.wantSafety())
return; // The already existing value will do just fine.
// TODO Upgrade this to a memset call when we have that available.
switch (ty.abiSize(self.target.*)) {
1 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaa }),
2 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaa }),
4 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
8 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
else => return self.fail(src, "TODO implement memset", .{}),
}
},
.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| {
const abi_size = ty.abiSize(self.target.*);
const adj_off = stack_offset + abi_size;
if (adj_off > 128) {
return self.fail(src, "TODO implement set stack variable with large stack offset", .{});
}
try self.code.ensureCapacity(self.code.items.len + 8);
switch (abi_size) {
1 => {
return self.fail(src, "TODO implement set abi_size=1 stack variable with immediate", .{});
},
2 => {
return self.fail(src, "TODO implement set abi_size=2 stack variable with immediate", .{});
},
4 => {
const x = @intCast(u32, x_big);
// 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, adj_off));
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);
},
8 => {
return self.fail(src, "TODO implement set abi_size=8 stack variable with immediate", .{});
},
else => {
return self.fail(src, "TODO implement set abi_size=large stack variable with immediate", .{});
},
}
if (x_big <= math.maxInt(u32)) {} 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| {
const abi_size = ty.abiSize(self.target.*);
const adj_off = stack_offset + abi_size;
try self.code.ensureCapacity(self.code.items.len + 7);
self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() });
const reg_id: u8 = @truncate(u3, reg.id());
if (adj_off <= 128) {
// example: 48 89 55 7f mov QWORD PTR [rbp+0x7f],rdx
const RM = @as(u8, 0b01_000_101) | (reg_id << 3);
const negative_offset = @intCast(i8, -@intCast(i32, adj_off));
const twos_comp = @bitCast(u8, negative_offset);
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM, twos_comp });
} else if (adj_off <= 2147483648) {
// example: 48 89 95 80 00 00 00 mov QWORD PTR [rbp+0x80],rdx
const RM = @as(u8, 0b10_000_101) | (reg_id << 3);
const negative_offset = @intCast(i32, -@intCast(i33, adj_off));
const twos_comp = @bitCast(u32, negative_offset);
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM });
mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp);
} else {
return self.fail(src, "stack offset too large", .{});
}
},
.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.
.undef => {
if (!self.wantSafety())
return; // The already existing value will do just fine.
// Write the debug undefined value.
switch (reg.size()) {
8 => return self.genSetReg(src, reg, .{ .immediate = 0xaa }),
16 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaa }),
32 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa }),
64 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
else => unreachable,
}
},
.compare_flags_unsigned => |op| {
try self.code.ensureCapacity(self.code.items.len + 3);
self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 });
// TODO audit this codegen: we force w = true here to make
// the value affect the big register
self.rex(.{ .b = reg.isExtended(), .w = true });
const opcode: u8 = switch (op) {
.gte => 0x93,
.gt => 0x97,
@ -1076,9 +1458,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.fail(src, "TODO set register with compare flags value (signed)", .{});
},
.immediate => |x| {
if (reg.size() != 64) {
return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
}
// 32-bit moves zero-extend to 64-bit, so xoring the 32-bit
// register is the fastest way to zero a register.
if (x == 0) {
@ -1131,16 +1510,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
//
// In this case, the encoding of the REX byte is 0b0100100B
try self.code.ensureCapacity(self.code.items.len + 10);
self.rex(.{ .w = true, .b = reg.isExtended() });
self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() });
self.code.items.len += 9;
self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111);
const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
mem.writeIntLittle(u64, imm_ptr, x);
},
.embedded_in_code => |code_offset| {
if (reg.size() != 64) {
return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
}
// We need the offset from RIP in a signed i32 twos complement.
// The instruction is 7 bytes long and RIP points to the next instruction.
try self.code.ensureCapacity(self.code.items.len + 7);
@ -1148,7 +1524,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three
// bits as five.
// REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id.
self.rex(.{ .w = true, .b = reg.isExtended() });
self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() });
self.code.items.len += 6;
const rip = self.code.items.len;
const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip);
@ -1160,12 +1536,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
},
.register => |src_reg| {
// If the registers are the same, nothing to do.
if (src_reg == reg)
if (src_reg.id() == reg.id())
return;
if (reg.size() != 64) {
return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
}
// This is a variant of 8B /r. Since we're using 64-bit moves, we require a REX.
// This is thus three bytes: REX 0x8B R/M.
// If the destination is extended, the R field must be 1.
@ -1173,14 +1546,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// Since the register is being accessed directly, the R/M mode is three. The reg field (the middle
// three bits) contain the destination, and the R/M field (the lower three bits) contain the source.
try self.code.ensureCapacity(self.code.items.len + 3);
self.rex(.{ .w = true, .r = reg.isExtended(), .b = src_reg.isExtended() });
self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended(), .b = src_reg.isExtended() });
const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @as(u8, src_reg.id() & 0b111);
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R });
},
.memory => |x| {
if (reg.size() != 64) {
return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
}
if (x <= math.maxInt(u32)) {
// Moving from memory to a register is a variant of `8B /r`.
// Since we're using 64-bit moves, we require a REX.
@ -1190,7 +1560,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// 0b00RRR100, where RRR is the lower three bits of the register ID.
// The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32.
try self.code.ensureCapacity(self.code.items.len + 8);
self.rex(.{ .w = true, .b = reg.isExtended() });
self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() });
self.code.appendSliceAssumeCapacity(&[_]u8{
0x8B,
0x04 | (@as(u8, reg.id() & 0b111) << 3), // R
@ -1218,7 +1588,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// is no way to possibly encode it. This means that RSP, RBP, R12, and R13 cannot be used with
// this instruction.
const id3 = @truncate(u3, reg.id());
std.debug.assert(id3 != 4 and id3 != 5);
assert(id3 != 4 and id3 != 5);
// Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue.
try self.genSetReg(src, reg, MCValue{ .immediate = x });
@ -1233,14 +1603,34 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
//
// Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both*
// register operands need to be marked as extended.
self.rex(.{ .w = true, .b = reg.isExtended(), .r = reg.isExtended() });
self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended(), .r = reg.isExtended() });
const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id());
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM });
}
}
},
.stack_offset => |off| {
return self.fail(src, "TODO implement genSetReg for stack variables", .{});
.stack_offset => |unadjusted_off| {
try self.code.ensureCapacity(self.code.items.len + 7);
const size_bytes = @divExact(reg.size(), 8);
const off = unadjusted_off + size_bytes;
self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() });
const reg_id: u8 = @truncate(u3, reg.id());
if (off <= 128) {
// Example: 48 8b 4d 7f mov rcx,QWORD PTR [rbp+0x7f]
const RM = @as(u8, 0b01_000_101) | (reg_id << 3);
const negative_offset = @intCast(i8, -@intCast(i32, off));
const twos_comp = @bitCast(u8, negative_offset);
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8b, RM, twos_comp });
} else if (off <= 2147483648) {
// Example: 48 8b 8d 80 00 00 00 mov rcx,QWORD PTR [rbp+0x80]
const RM = @as(u8, 0b10_000_101) | (reg_id << 3);
const negative_offset = @intCast(i32, -@intCast(i33, off));
const twos_comp = @bitCast(u32, negative_offset);
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8b, RM });
mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp);
} else {
return self.fail(src, "stack offset too large", .{});
}
},
},
else => return self.fail(src, "TODO implement getSetReg for {}", .{self.target.cpu.arch}),
@ -1279,24 +1669,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
@ -1324,6 +1696,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) !MCValue {
if (typed_value.val.isUndef())
return MCValue.undef;
const ptr_bits = self.target.cpu.arch.ptrBitWidth();
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
switch (typed_value.ty.zigTypeTag()) {
@ -1398,11 +1772,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
for (param_types) |ty, i| {
switch (ty.zigTypeTag()) {
.Bool, .Int => {
const param_size = @intCast(u32, ty.abiSize(self.target.*));
if (next_int_reg >= c_abi_int_param_regs.len) {
result.args[i] = .{ .stack_offset = next_stack_offset };
next_stack_offset += @intCast(u32, ty.abiSize(self.target.*));
next_stack_offset += param_size;
} else {
result.args[i] = .{ .register = c_abi_int_param_regs[next_int_reg] };
const aliased_reg = registerAlias(
c_abi_int_param_regs[next_int_reg],
param_size,
);
result.args[i] = .{ .register = aliased_reg };
next_int_reg += 1;
}
},
@ -1426,7 +1805,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.x86_64 => switch (cc) {
.Naked => unreachable,
.Unspecified, .C => {
result.return_value = .{ .register = c_abi_int_return_regs[0] };
const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*));
const aliased_reg = registerAlias(c_abi_int_return_regs[0], ret_ty_size);
result.return_value = .{ .register = aliased_reg };
},
else => return self.fail(src, "TODO implement function return values for {}", .{cc}),
},
@ -1435,6 +1816,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return result;
}
/// TODO support scope overrides. Also note this logic is duplicated with `Module.wantSafety`.
fn wantSafety(self: *Self) bool {
return switch (self.bin_file.base.options.optimize_mode) {
.Debug => true,
.ReleaseSafe => true,
.ReleaseFast => false,
.ReleaseSmall => false,
};
}
fn fail(self: *Self, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } {
@setCold(true);
assert(self.err_msg == null);
@ -1463,5 +1854,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn parseRegName(name: []const u8) ?Register {
return std.meta.stringToEnum(Register, name);
}
fn registerAlias(reg: Register, size_bytes: u32) Register {
switch (arch) {
// For x86_64 we have to pick a smaller register alias depending on abi size.
.x86_64 => switch (size_bytes) {
1 => return reg.to8(),
2 => return reg.to16(),
4 => return reg.to32(),
8 => return reg.to64(),
else => unreachable,
},
else => return reg,
}
}
};
}

View File

@ -81,6 +81,26 @@ pub const Register = enum(u8) {
else => null,
};
}
/// Convert from any register to its 64 bit alias.
pub fn to64(self: Register) Register {
return @intToEnum(Register, self.id());
}
/// Convert from any register to its 32 bit alias.
pub fn to32(self: Register) Register {
return @intToEnum(Register, @as(u8, self.id()) + 16);
}
/// Convert from any register to its 16 bit alias.
pub fn to16(self: Register) Register {
return @intToEnum(Register, @as(u8, self.id()) + 32);
}
/// Convert from any register to its 8 bit alias.
pub fn to8(self: Register) Register {
return @intToEnum(Register, @as(u8, self.id()) + 48);
}
};
// zig fmt: on

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,
@ -157,8 +165,7 @@ pub const Inst = struct {
/// Returns `null` if runtime-known.
pub fn value(base: *Inst) ?Value {
if (base.ty.onePossibleValue())
return Value.initTag(.the_one_possible_value);
if (base.ty.onePossibleValue()) |opv| return opv;
const inst = base.cast(Constant) orelse return null;
return inst.val;

View File

@ -16,6 +16,7 @@ pub const Options = struct {
output_mode: std.builtin.OutputMode,
link_mode: std.builtin.LinkMode,
object_format: std.builtin.ObjectFormat,
optimize_mode: std.builtin.Mode,
/// Used for calculating how much space to reserve for symbols in case the binary file
/// does not already have a symbol table.
symbol_count_hint: u64 = 32,
@ -66,6 +67,7 @@ pub fn writeFilePath(
.link_mode = module.link_mode,
.object_format = module.object_format,
.symbol_count_hint = module.decls.items.len,
.optimize_mode = module.optimize_mode,
};
const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(options) });
defer af.deinit();
@ -88,9 +90,12 @@ pub fn writeFilePath(
fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C {
return File.C{
.base = .{
.tag = .c,
.options = options,
},
.allocator = allocator,
.file = file,
.options = options,
.main = std.ArrayList(u8).init(allocator),
.header = std.ArrayList(u8).init(allocator),
.constants = std.ArrayList(u8).init(allocator),
@ -114,6 +119,8 @@ pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !File
pub const File = struct {
tag: Tag,
options: Options,
pub fn cast(base: *File, comptime T: type) ?*T {
if (base.tag != T.base_tag)
return null;
@ -123,47 +130,47 @@ pub const File = struct {
pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void {
switch (base.tag) {
.Elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path),
.C => {},
.elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path),
.c => {},
}
}
pub fn makeExecutable(base: *File) !void {
switch (base.tag) {
.Elf => return @fieldParentPtr(Elf, "base", base).makeExecutable(),
.C => unreachable,
.elf => return @fieldParentPtr(Elf, "base", base).makeExecutable(),
.c => unreachable,
}
}
pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void {
switch (base.tag) {
.Elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
.C => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
.elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
.c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
}
}
pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void {
switch (base.tag) {
.Elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
.C => {},
.elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
.c => {},
}
}
pub fn deinit(base: *File) void {
switch (base.tag) {
.Elf => @fieldParentPtr(Elf, "base", base).deinit(),
.C => @fieldParentPtr(C, "base", base).deinit(),
.elf => @fieldParentPtr(Elf, "base", base).deinit(),
.c => @fieldParentPtr(C, "base", base).deinit(),
}
}
pub fn destroy(base: *File) void {
switch (base.tag) {
.Elf => {
.elf => {
const parent = @fieldParentPtr(Elf, "base", base);
parent.deinit();
parent.allocator.destroy(parent);
},
.C => {
.c => {
const parent = @fieldParentPtr(C, "base", base);
parent.deinit();
parent.allocator.destroy(parent);
@ -173,29 +180,22 @@ pub const File = struct {
pub fn flush(base: *File) !void {
try switch (base.tag) {
.Elf => @fieldParentPtr(Elf, "base", base).flush(),
.C => @fieldParentPtr(C, "base", base).flush(),
.elf => @fieldParentPtr(Elf, "base", base).flush(),
.c => @fieldParentPtr(C, "base", base).flush(),
};
}
pub fn freeDecl(base: *File, decl: *Module.Decl) void {
switch (base.tag) {
.Elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
.C => unreachable,
.elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
.c => unreachable,
}
}
pub fn errorFlags(base: *File) ErrorFlags {
return switch (base.tag) {
.Elf => @fieldParentPtr(Elf, "base", base).error_flags,
.C => return .{ .no_entry_point_found = false },
};
}
pub fn options(base: *File) Options {
return switch (base.tag) {
.Elf => @fieldParentPtr(Elf, "base", base).options,
.C => @fieldParentPtr(C, "base", base).options,
.elf => @fieldParentPtr(Elf, "base", base).error_flags,
.c => return .{ .no_entry_point_found = false },
};
}
@ -207,14 +207,14 @@ pub const File = struct {
exports: []const *Module.Export,
) !void {
switch (base.tag) {
.Elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
.C => return {},
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
.c => return {},
}
}
pub const Tag = enum {
Elf,
C,
elf,
c,
};
pub const ErrorFlags = struct {
@ -222,15 +222,15 @@ pub const File = struct {
};
pub const C = struct {
pub const base_tag: Tag = .C;
base: File = File{ .tag = base_tag },
pub const base_tag: Tag = .c;
base: File,
allocator: *Allocator,
header: std.ArrayList(u8),
constants: std.ArrayList(u8),
main: std.ArrayList(u8),
file: ?fs.File,
options: Options,
called: std.StringHashMap(void),
need_stddef: bool = false,
need_stdint: bool = false,
@ -294,13 +294,13 @@ pub const File = struct {
};
pub const Elf = struct {
pub const base_tag: Tag = .Elf;
base: File = File{ .tag = base_tag },
pub const base_tag: Tag = .elf;
base: File,
allocator: *Allocator,
file: ?fs.File,
owns_file_handle: bool,
options: Options,
ptr_width: enum { p32, p64 },
/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
@ -460,13 +460,13 @@ pub const File = struct {
self.file = try dir.createFile(sub_path, .{
.truncate = false,
.read = true,
.mode = determineMode(self.options),
.mode = determineMode(self.base.options),
});
}
/// Returns end pos of collision, if any.
fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {
const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32;
const small_ptr = self.base.options.target.cpu.arch.ptrBitWidth() == 32;
const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);
if (start < ehdr_size)
return ehdr_size;
@ -569,7 +569,7 @@ pub const File = struct {
};
if (self.phdr_load_re_index == null) {
self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len);
const file_size = self.options.program_code_size_hint;
const file_size = self.base.options.program_code_size_hint;
const p_align = 0x1000;
const off = self.findFreeSpace(file_size, p_align);
std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
@ -588,7 +588,7 @@ pub const File = struct {
}
if (self.phdr_got_index == null) {
self.phdr_got_index = @intCast(u16, self.program_headers.items.len);
const file_size = @as(u64, ptr_size) * self.options.symbol_count_hint;
const file_size = @as(u64, ptr_size) * self.base.options.symbol_count_hint;
// We really only need ptr alignment but since we are using PROGBITS, linux requires
// page align.
const p_align = 0x1000;
@ -671,7 +671,7 @@ pub const File = struct {
self.symtab_section_index = @intCast(u16, self.sections.items.len);
const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym);
const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym);
const file_size = self.options.symbol_count_hint * each_size;
const file_size = self.base.options.symbol_count_hint * each_size;
const off = self.findFreeSpace(file_size, min_align);
std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
@ -726,7 +726,7 @@ pub const File = struct {
/// Commit pending changes and write headers.
pub fn flush(self: *Elf) !void {
const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
// Unfortunately these have to be buffered and done at the end because ELF does not allow
// mixing local and global symbols within a symbol table.
@ -845,7 +845,7 @@ pub const File = struct {
}
self.shdr_table_dirty = false;
}
if (self.entry_addr == null and self.options.output_mode == .Exe) {
if (self.entry_addr == null and self.base.options.output_mode == .Exe) {
std.log.debug(.link, "no_entry_point_found = true\n", .{});
self.error_flags.no_entry_point_found = true;
} else {
@ -875,7 +875,7 @@ pub const File = struct {
};
index += 1;
const endian = self.options.target.cpu.arch.endian();
const endian = self.base.options.target.cpu.arch.endian();
hdr_buf[index] = switch (endian) {
.Little => elf.ELFDATA2LSB,
.Big => elf.ELFDATA2MSB,
@ -893,10 +893,10 @@ pub const File = struct {
assert(index == 16);
const elf_type = switch (self.options.output_mode) {
const elf_type = switch (self.base.options.output_mode) {
.Exe => elf.ET.EXEC,
.Obj => elf.ET.REL,
.Lib => switch (self.options.link_mode) {
.Lib => switch (self.base.options.link_mode) {
.Static => elf.ET.REL,
.Dynamic => elf.ET.DYN,
},
@ -904,7 +904,7 @@ pub const File = struct {
mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian);
index += 2;
const machine = self.options.target.cpu.arch.toElfMachine();
const machine = self.base.options.target.cpu.arch.toElfMachine();
mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian);
index += 2;
@ -1216,7 +1216,7 @@ pub const File = struct {
},
};
const required_alignment = typed_value.ty.abiAlignment(self.options.target);
const required_alignment = typed_value.ty.abiAlignment(self.base.options.target);
const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) {
.Fn => elf.STT_FUNC,
@ -1361,9 +1361,9 @@ pub const File = struct {
}
fn writeProgHeader(self: *Elf, index: usize) !void {
const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const offset = self.program_headers.items[index].p_offset;
switch (self.options.target.cpu.arch.ptrBitWidth()) {
switch (self.base.options.target.cpu.arch.ptrBitWidth()) {
32 => {
var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])};
if (foreign_endian) {
@ -1383,9 +1383,9 @@ pub const File = struct {
}
fn writeSectHeader(self: *Elf, index: usize) !void {
const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const offset = self.sections.items[index].sh_offset;
switch (self.options.target.cpu.arch.ptrBitWidth()) {
switch (self.base.options.target.cpu.arch.ptrBitWidth()) {
32 => {
var shdr: [1]elf.Elf32_Shdr = undefined;
shdr[0] = sectHeaderTo32(self.sections.items[index]);
@ -1433,7 +1433,7 @@ pub const File = struct {
self.offset_table_count_dirty = false;
}
const endian = self.options.target.cpu.arch.endian();
const endian = self.base.options.target.cpu.arch.endian();
const off = shdr.sh_offset + @as(u64, entry_size) * index;
switch (self.ptr_width) {
.p32 => {
@ -1475,7 +1475,7 @@ pub const File = struct {
syms_sect.sh_size = needed_size; // anticipating adding the global symbols later
self.shdr_table_dirty = true; // TODO look into only writing one section
}
const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
switch (self.ptr_width) {
.p32 => {
var sym = [1]elf.Elf32_Sym{
@ -1511,7 +1511,7 @@ pub const File = struct {
.p32 => @sizeOf(elf.Elf32_Sym),
.p64 => @sizeOf(elf.Elf64_Sym),
};
const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size;
switch (self.ptr_width) {
.p32 => {
@ -1577,9 +1577,12 @@ pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !Fi
}
var self: File.Elf = .{
.base = .{
.tag = .elf,
.options = options,
},
.allocator = allocator,
.file = file,
.options = options,
.ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
32 => .p32,
64 => .p64,
@ -1637,10 +1640,13 @@ fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !Fil
.raw => return error.IncrFailed,
}
var self: File.Elf = .{
.base = .{
.tag = .elf,
.options = options,
},
.allocator = allocator,
.file = file,
.owns_file_handle = false,
.options = options,
.ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
32 => .p32,
64 => .p64,

View File

@ -380,7 +380,7 @@ pub const Type = extern union {
},
.single_mut_pointer => {
const payload = @fieldParentPtr(Payload.SingleMutPointer, "base", ty.ptr_otherwise);
try out_stream.writeAll("* ");
try out_stream.writeAll("*");
ty = payload.pointee_type;
continue;
},
@ -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()) {
@ -1601,7 +1653,7 @@ pub const Type = extern union {
};
}
pub fn onePossibleValue(self: Type) bool {
pub fn onePossibleValue(self: Type) ?Value {
var ty = self;
while (true) switch (ty.tag()) {
.f16,
@ -1640,21 +1692,32 @@ pub const Type = extern union {
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
=> return false,
.c_void,
.void,
.noreturn,
.@"null",
.@"undefined",
=> return true,
=> return null,
.int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0,
.int_signed => return ty.cast(Payload.IntSigned).?.bits == 0,
.void => return Value.initTag(.void_value),
.noreturn => return Value.initTag(.unreachable_value),
.@"null" => return Value.initTag(.null_value),
.@"undefined" => return Value.initTag(.undef),
.int_unsigned => {
if (ty.cast(Payload.IntUnsigned).?.bits == 0) {
return Value.initTag(.zero);
} else {
return null;
}
},
.int_signed => {
if (ty.cast(Payload.IntSigned).?.bits == 0) {
return Value.initTag(.zero);
} else {
return null;
}
},
.array => {
const array = ty.cast(Payload.Array).?;
if (array.len == 0)
return true;
return Value.initTag(.empty_array);
ty = array.elem_type;
continue;
},

View File

@ -63,7 +63,9 @@ pub const Value = extern union {
undef,
zero,
the_one_possible_value, // when the type only has one possible value
void_value,
unreachable_value,
empty_array,
null_value,
bool_true,
bool_false, // See last_no_payload_tag below.
@ -164,7 +166,9 @@ pub const Value = extern union {
.const_slice_u8_type,
.undef,
.zero,
.the_one_possible_value,
.void_value,
.unreachable_value,
.empty_array,
.null_value,
.bool_true,
.bool_false,
@ -285,7 +289,8 @@ pub const Value = extern union {
.null_value => return out_stream.writeAll("null"),
.undef => return out_stream.writeAll("undefined"),
.zero => return out_stream.writeAll("0"),
.the_one_possible_value => return out_stream.writeAll("(one possible value)"),
.void_value => return out_stream.writeAll("{}"),
.unreachable_value => return out_stream.writeAll("unreachable"),
.bool_true => return out_stream.writeAll("true"),
.bool_false => return out_stream.writeAll("false"),
.ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream),
@ -312,6 +317,7 @@ pub const Value = extern union {
try out_stream.print("&[{}] ", .{elem_ptr.index});
val = elem_ptr.array_ptr;
},
.empty_array => return out_stream.writeAll(".{}"),
.bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream),
.repeated => {
try out_stream.writeAll("(repeated) ");
@ -388,7 +394,9 @@ pub const Value = extern union {
.undef,
.zero,
.the_one_possible_value,
.void_value,
.unreachable_value,
.empty_array,
.bool_true,
.bool_false,
.null_value,
@ -460,15 +468,18 @@ pub const Value = extern union {
.decl_ref,
.elem_ptr,
.bytes,
.undef,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
.void_value,
.unreachable_value,
.empty_array,
=> unreachable,
.the_one_possible_value, // An integer with one possible value is always zero.
.undef => unreachable,
.zero,
.bool_false,
=> return BigIntMutable.init(&space.limbs, 0).toConst(),
@ -532,16 +543,19 @@ pub const Value = extern union {
.decl_ref,
.elem_ptr,
.bytes,
.undef,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
.void_value,
.unreachable_value,
.empty_array,
=> unreachable,
.undef => unreachable,
.zero,
.the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> return 0,
@ -570,7 +584,7 @@ pub const Value = extern union {
.float_64 => @floatCast(T, self.cast(Payload.Float_64).?.val),
.float_128 => @floatCast(T, self.cast(Payload.Float_128).?.val),
.zero, .the_one_possible_value => 0,
.zero => 0,
.int_u64 => @intToFloat(T, self.cast(Payload.Int_u64).?.int),
// .int_i64 => @intToFloat(f128, self.cast(Payload.Int_i64).?.int),
.int_i64 => @panic("TODO lld: error: undefined symbol: __floatditf"),
@ -637,9 +651,11 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
.void_value,
.unreachable_value,
.empty_array,
=> unreachable,
.the_one_possible_value, // an integer with one possible value is always zero
.zero,
.bool_false,
=> return 0,
@ -714,11 +730,13 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
.void_value,
.unreachable_value,
.empty_array,
=> unreachable,
.zero,
.undef,
.the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> return true,
@ -797,13 +815,13 @@ pub const Value = extern union {
// return Value.initPayload(&res_payload.base).copy(allocator);
},
32 => {
var res_payload = Value.Payload.Float_32{.val = self.toFloat(f32)};
var res_payload = Value.Payload.Float_32{ .val = self.toFloat(f32) };
if (!self.eql(Value.initPayload(&res_payload.base)))
return error.Overflow;
return Value.initPayload(&res_payload.base).copy(allocator);
},
64 => {
var res_payload = Value.Payload.Float_64{.val = self.toFloat(f64)};
var res_payload = Value.Payload.Float_64{ .val = self.toFloat(f64) };
if (!self.eql(Value.initPayload(&res_payload.base)))
return error.Overflow;
return Value.initPayload(&res_payload.base).copy(allocator);
@ -875,7 +893,9 @@ pub const Value = extern union {
.int_i64,
.int_big_positive,
.int_big_negative,
.the_one_possible_value,
.empty_array,
.void_value,
.unreachable_value,
=> unreachable,
.zero => false,
@ -939,10 +959,12 @@ pub const Value = extern union {
.bytes,
.repeated,
.undef,
.void_value,
.unreachable_value,
.empty_array,
=> unreachable,
.zero,
.the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> .eq,
@ -964,8 +986,8 @@ pub const Value = extern union {
pub fn order(lhs: Value, rhs: Value) std.math.Order {
const lhs_tag = lhs.tag();
const rhs_tag = rhs.tag();
const lhs_is_zero = lhs_tag == .zero or lhs_tag == .the_one_possible_value;
const rhs_is_zero = rhs_tag == .zero or rhs_tag == .the_one_possible_value;
const lhs_is_zero = lhs_tag == .zero;
const rhs_is_zero = rhs_tag == .zero;
if (lhs_is_zero) return rhs.orderAgainstZero().invert();
if (rhs_is_zero) return lhs.orderAgainstZero();
@ -1071,9 +1093,11 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
.void_value,
.unreachable_value,
.empty_array,
=> unreachable,
.the_one_possible_value => Value.initTag(.the_one_possible_value),
.ref_val => self.cast(Payload.RefVal).?.val,
.decl_ref => self.cast(Payload.DeclRef).?.decl.value(),
.elem_ptr => {
@ -1130,7 +1154,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.zero,
.the_one_possible_value,
.bool_true,
.bool_false,
.null_value,
@ -1147,8 +1170,12 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
.void_value,
.unreachable_value,
=> unreachable,
.empty_array => unreachable, // out of bounds array index
.bytes => {
const int_payload = try allocator.create(Payload.Int_u64);
int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] };
@ -1175,8 +1202,7 @@ pub const Value = extern union {
return self.tag() == .undef;
}
/// Valid for all types. Asserts the value is not undefined.
/// `.the_one_possible_value` is reported as not null.
/// Valid for all types. Asserts the value is not undefined and not unreachable.
pub fn isNull(self: Value) bool {
return switch (self.tag()) {
.ty,
@ -1221,7 +1247,7 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.zero,
.the_one_possible_value,
.empty_array,
.bool_true,
.bool_false,
.function,
@ -1238,9 +1264,11 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
.void_value,
=> false,
.undef => unreachable,
.unreachable_value => unreachable,
.null_value => true,
};
}

View File

@ -183,6 +183,10 @@ pub const Inst = struct {
shl,
/// Integer shift-right. Arithmetic or logical depending on the signedness of the integer type.
shr,
/// Create a const pointer type based on the element type. `*const T`
single_const_ptr_type,
/// Create a mutable pointer type based on the element type. `*T`
single_mut_ptr_type,
/// Write a value to a pointer. For loading, see `deref`.
store,
/// String Literal. Makes an anonymous Decl and then takes a pointer to it.
@ -228,6 +232,8 @@ pub const Inst = struct {
.ref,
.bitcast_lvalue,
.typeof,
.single_const_ptr_type,
.single_mut_ptr_type,
=> UnOp,
.add,
@ -242,6 +248,7 @@ pub const Inst = struct {
.mulwrap,
.shl,
.shr,
.store,
.sub,
.subwrap,
.cmp_lt,
@ -270,7 +277,6 @@ pub const Inst = struct {
.coerce_result_block_ptr => CoerceResultBlockPtr,
.compileerror => CompileError,
.@"const" => Const,
.store => Store,
.str => Str,
.int => Int,
.inttype => IntType,
@ -348,6 +354,8 @@ pub const Inst = struct {
.ret_type,
.shl,
.shr,
.single_const_ptr_type,
.single_mut_ptr_type,
.store,
.str,
.sub,
@ -545,17 +553,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,
@ -745,7 +742,7 @@ pub const Inst = struct {
.@"false" => .{ .ty = Type.initTag(.bool), .val = Value.initTag(.bool_false) },
.@"null" => .{ .ty = Type.initTag(.@"null"), .val = Value.initTag(.null_value) },
.@"undefined" => .{ .ty = Type.initTag(.@"undefined"), .val = Value.initTag(.undef) },
.void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.the_one_possible_value) },
.void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.void_value) },
};
}
};
@ -1601,6 +1598,21 @@ const EmitZIR = struct {
const decl = decl_ref.decl;
return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl));
}
if (typed_value.val.isUndef()) {
const as_inst = try self.arena.allocator.create(Inst.BinOp);
as_inst.* = .{
.base = .{
.tag = .as,
.src = src,
},
.positionals = .{
.lhs = (try self.emitType(src, typed_value.ty)).inst,
.rhs = (try self.emitPrimitive(src, .@"undefined")).inst,
},
.kw_args = .{},
};
return self.emitUnnamedDecl(&as_inst.base);
}
switch (typed_value.ty.zigTypeTag()) {
.Pointer => {
const ptr_elem_type = typed_value.ty.elemType();
@ -1837,9 +1849,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),
@ -2103,6 +2118,25 @@ const EmitZIR = struct {
};
return self.emitUnnamedDecl(&inttype_inst.base);
},
.Pointer => {
if (ty.isSinglePointer()) {
const inst = try self.arena.allocator.create(Inst.UnOp);
const tag: Inst.Tag = if (ty.isConstPtr()) .single_const_ptr_type else .single_mut_ptr_type;
inst.* = .{
.base = .{
.src = src,
.tag = tag,
},
.positionals = .{
.operand = (try self.emitType(src, ty.elemType())).inst,
},
.kw_args = .{},
};
return self.emitUnnamedDecl(&inst.base);
} else {
std.debug.panic("TODO implement emitType for {}", .{ty});
}
},
else => std.debug.panic("TODO implement emitType for {}", .{ty}),
},
}

View File

@ -50,6 +50,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
.ref => return analyzeInstRef(mod, scope, old_inst.castTag(.ref).?),
.ret_ptr => return analyzeInstRetPtr(mod, scope, old_inst.castTag(.ret_ptr).?),
.ret_type => return analyzeInstRetType(mod, scope, old_inst.castTag(.ret_type).?),
.single_const_ptr_type => return analyzeInstSingleConstPtrType(mod, scope, old_inst.castTag(.single_const_ptr_type).?),
.single_mut_ptr_type => return analyzeInstSingleMutPtrType(mod, scope, old_inst.castTag(.single_mut_ptr_type).?),
.store => return analyzeInstStore(mod, scope, old_inst.castTag(.store).?),
.str => return analyzeInstStr(mod, scope, old_inst.castTag(.str).?),
.int => {
@ -287,8 +289,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 +301,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 +341,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 {
@ -872,7 +882,7 @@ fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn
fn analyzeInstComptimeOp(mod: *Module, scope: *Scope, res_type: Type, inst: *zir.Inst.BinOp, lhs_val: Value, rhs_val: Value) InnerError!*Inst {
// incase rhs is 0, simply return lhs without doing any calculations
// TODO Once division is implemented we should throw an error when dividing by 0.
if (rhs_val.tag() == .zero or rhs_val.tag() == .the_one_possible_value) {
if (rhs_val.compareWithZero(.eq)) {
return mod.constInst(scope, inst.base.src, .{
.ty = res_type,
.val = lhs_val,
@ -1073,6 +1083,7 @@ fn analyzeInstUnreachNoChk(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp)
fn analyzeInstUnreachable(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst {
const b = try mod.requireRuntimeBlock(scope, unreach.base.src);
// TODO Add compile error for @optimizeFor occurring too late in a scope.
if (mod.wantSafety(scope)) {
// TODO Once we have a panic function to call, call it here instead of this.
_ = try mod.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint);
@ -1129,3 +1140,15 @@ fn analyzeDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerErr
return decl;
}
fn analyzeInstSingleConstPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
const elem_type = try resolveType(mod, scope, inst.positionals.operand);
const ty = try mod.singleConstPtrType(scope, inst.base.src, elem_type);
return mod.constType(scope, inst.base.src, ty);
}
fn analyzeInstSingleMutPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
const elem_type = try resolveType(mod, scope, inst.positionals.operand);
const ty = try mod.singleMutPtrType(scope, inst.base.src, elem_type);
return mod.constType(scope, inst.base.src, ty);
}

View File

@ -363,5 +363,39 @@ pub fn addCases(ctx: *TestContext) !void {
,
"",
);
// Local mutable variables.
case.addCompareOutput(
\\export fn _start() noreturn {
\\ assert(add(3, 4) == 7);
\\ assert(add(20, 10) == 30);
\\
\\ exit();
\\}
\\
\\fn add(a: u32, b: u32) u32 {
\\ var x: u32 = undefined;
\\ x = 0;
\\ x += a;
\\ x += b;
\\ return x;
\\}
\\
\\pub fn assert(ok: bool) void {
\\ if (!ok) unreachable; // assertion failure
\\}
\\
\\fn exit() noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (0)
\\ : "rcx", "r11", "memory"
\\ );
\\ unreachable;
\\}
,
"",
);
}
}