Merge branch 'stage2-condbr'
This commit is contained in:
@@ -1210,6 +1210,13 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
|
||||
|
||||
try self.astGenBlock(&gen_scope.base, body_block);
|
||||
|
||||
if (!fn_type.fnReturnType().isNoReturn() and (gen_scope.instructions.items.len == 0 or
|
||||
!gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn()))
|
||||
{
|
||||
const src = tree.token_locs[body_block.rbrace].start;
|
||||
_ = try self.addZIRInst(&gen_scope.base, src, zir.Inst.ReturnVoid, .{}, .{});
|
||||
}
|
||||
|
||||
const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR);
|
||||
fn_zir.* = .{
|
||||
.body = .{
|
||||
@@ -2686,7 +2693,7 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr
|
||||
|
||||
// Blocks must terminate with noreturn instruction.
|
||||
assert(child_block.instructions.items.len != 0);
|
||||
assert(child_block.instructions.items[child_block.instructions.items.len - 1].tag.isNoReturn());
|
||||
assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn());
|
||||
|
||||
// Need to set the type and emit the Block instruction. This allows machine code generation
|
||||
// to emit a jump instruction to after the block when it encounters the break.
|
||||
@@ -3271,7 +3278,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner
|
||||
defer false_block.instructions.deinit(self.gpa);
|
||||
try self.analyzeBody(&false_block.base, inst.positionals.false_body);
|
||||
|
||||
return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){
|
||||
return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.noreturn), Inst.CondBr, Inst.Args(Inst.CondBr){
|
||||
.condition = cond,
|
||||
.true_body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) },
|
||||
.false_body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) },
|
||||
@@ -3518,9 +3525,26 @@ fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type {
|
||||
fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type {
|
||||
if (instructions.len == 0)
|
||||
return Type.initTag(.noreturn);
|
||||
|
||||
if (instructions.len == 1)
|
||||
return instructions[0].ty;
|
||||
return self.fail(scope, instructions[0].src, "TODO peer type resolution", .{});
|
||||
|
||||
var prev_inst = instructions[0];
|
||||
for (instructions[1..]) |next_inst| {
|
||||
if (next_inst.ty.eql(prev_inst.ty))
|
||||
continue;
|
||||
if (next_inst.ty.zigTypeTag() == .NoReturn)
|
||||
continue;
|
||||
if (prev_inst.ty.zigTypeTag() == .NoReturn) {
|
||||
prev_inst = next_inst;
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO error notes pointing out each type
|
||||
return self.fail(scope, next_inst.src, "incompatible types: '{}' and '{}'", .{ prev_inst.ty, next_inst.ty });
|
||||
}
|
||||
|
||||
return prev_inst.ty;
|
||||
}
|
||||
|
||||
fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
|
||||
|
||||
@@ -53,10 +53,8 @@ pub fn generateSymbol(
|
||||
const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen());
|
||||
defer bin_file.allocator.free(param_types);
|
||||
fn_type.fnParamTypes(param_types);
|
||||
// A parameter may be broken into multiple machine code parameters, so we don't
|
||||
// know the size up front.
|
||||
var mc_args = try std.ArrayList(Function.MCValue).initCapacity(bin_file.allocator, param_types.len);
|
||||
defer mc_args.deinit();
|
||||
var mc_args = try bin_file.allocator.alloc(MCValue, param_types.len);
|
||||
defer bin_file.allocator.free(mc_args);
|
||||
|
||||
var branch_stack = std.ArrayList(Function.Branch).init(bin_file.allocator);
|
||||
defer {
|
||||
@@ -67,57 +65,6 @@ pub fn generateSymbol(
|
||||
const branch = try branch_stack.addOne();
|
||||
branch.* = .{};
|
||||
|
||||
switch (fn_type.fnCallingConvention()) {
|
||||
.Naked => assert(mc_args.items.len == 0),
|
||||
.Unspecified, .C => {
|
||||
// Prepare the function parameters
|
||||
switch (bin_file.options.target.cpu.arch) {
|
||||
.x86_64 => {
|
||||
const integer_registers = [_]Reg(.x86_64){ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
|
||||
var next_int_reg: usize = 0;
|
||||
|
||||
for (param_types) |param_type, src_i| {
|
||||
switch (param_type.zigTypeTag()) {
|
||||
.Bool, .Int => {
|
||||
if (next_int_reg >= integer_registers.len) {
|
||||
try mc_args.append(.{ .stack_offset = branch.next_stack_offset });
|
||||
branch.next_stack_offset += @intCast(u32, param_type.abiSize(bin_file.options.target));
|
||||
} else {
|
||||
try mc_args.append(.{ .register = @enumToInt(integer_registers[next_int_reg]) });
|
||||
next_int_reg += 1;
|
||||
}
|
||||
},
|
||||
else => return Result{
|
||||
.fail = try ErrorMsg.create(
|
||||
bin_file.allocator,
|
||||
src,
|
||||
"TODO implement function parameters of type {}",
|
||||
.{@tagName(param_type.zigTypeTag())},
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
else => return Result{
|
||||
.fail = try ErrorMsg.create(
|
||||
bin_file.allocator,
|
||||
src,
|
||||
"TODO implement function parameters for {}",
|
||||
.{bin_file.options.target.cpu.arch},
|
||||
),
|
||||
},
|
||||
}
|
||||
},
|
||||
else => return Result{
|
||||
.fail = try ErrorMsg.create(
|
||||
bin_file.allocator,
|
||||
src,
|
||||
"TODO implement {} calling convention",
|
||||
.{fn_type.fnCallingConvention()},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
var function = Function{
|
||||
.gpa = bin_file.allocator,
|
||||
.target = &bin_file.options.target,
|
||||
@@ -125,11 +72,17 @@ pub fn generateSymbol(
|
||||
.mod_fn = module_fn,
|
||||
.code = code,
|
||||
.err_msg = null,
|
||||
.args = mc_args.items,
|
||||
.args = mc_args,
|
||||
.branch_stack = &branch_stack,
|
||||
.src = src,
|
||||
};
|
||||
|
||||
const cc = fn_type.fnCallingConvention();
|
||||
branch.max_end_stack = function.resolveParameters(src, cc, param_types, mc_args) catch |err| switch (err) {
|
||||
error.CodegenFail => return Result{ .fail = function.err_msg.? },
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
branch.max_end_stack = branch.next_stack_offset;
|
||||
function.gen() catch |err| switch (err) {
|
||||
error.CodegenFail => return Result{ .fail = function.err_msg.? },
|
||||
else => |e| return e,
|
||||
@@ -235,6 +188,65 @@ const InnerError = error{
|
||||
CodegenFail,
|
||||
};
|
||||
|
||||
const MCValue = union(enum) {
|
||||
/// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc.
|
||||
none,
|
||||
/// Control flow will not allow this value to be observed.
|
||||
unreach,
|
||||
/// No more references to this value remain.
|
||||
dead,
|
||||
/// A pointer-sized integer that fits in a register.
|
||||
immediate: u64,
|
||||
/// The constant was emitted into the code, at this offset.
|
||||
embedded_in_code: usize,
|
||||
/// The value is in a target-specific register. The value can
|
||||
/// be @intToEnum casted to the respective Reg enum.
|
||||
register: usize,
|
||||
/// The value is in memory at a hard-coded address.
|
||||
memory: u64,
|
||||
/// The value is one of the stack variables.
|
||||
stack_offset: u64,
|
||||
/// The value is in the compare flags assuming an unsigned operation,
|
||||
/// with this operator applied on top of it.
|
||||
compare_flags_unsigned: std.math.CompareOperator,
|
||||
/// The value is in the compare flags assuming a signed operation,
|
||||
/// with this operator applied on top of it.
|
||||
compare_flags_signed: std.math.CompareOperator,
|
||||
|
||||
fn isMemory(mcv: MCValue) bool {
|
||||
return switch (mcv) {
|
||||
.embedded_in_code, .memory, .stack_offset => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn isImmediate(mcv: MCValue) bool {
|
||||
return switch (mcv) {
|
||||
.immediate => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn isMutable(mcv: MCValue) bool {
|
||||
return switch (mcv) {
|
||||
.none => unreachable,
|
||||
.unreach => unreachable,
|
||||
.dead => unreachable,
|
||||
|
||||
.immediate,
|
||||
.embedded_in_code,
|
||||
.memory,
|
||||
.compare_flags_unsigned,
|
||||
.compare_flags_signed,
|
||||
=> false,
|
||||
|
||||
.register,
|
||||
.stack_offset,
|
||||
=> true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const Function = struct {
|
||||
gpa: *Allocator,
|
||||
bin_file: *link.File.Elf,
|
||||
@@ -243,6 +255,7 @@ const Function = struct {
|
||||
code: *std.ArrayList(u8),
|
||||
err_msg: ?*ErrorMsg,
|
||||
args: []MCValue,
|
||||
src: 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"
|
||||
@@ -284,65 +297,6 @@ const Function = struct {
|
||||
size: u32,
|
||||
};
|
||||
|
||||
const MCValue = union(enum) {
|
||||
/// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc.
|
||||
none,
|
||||
/// Control flow will not allow this value to be observed.
|
||||
unreach,
|
||||
/// No more references to this value remain.
|
||||
dead,
|
||||
/// A pointer-sized integer that fits in a register.
|
||||
immediate: u64,
|
||||
/// The constant was emitted into the code, at this offset.
|
||||
embedded_in_code: usize,
|
||||
/// The value is in a target-specific register. The value can
|
||||
/// be @intToEnum casted to the respective Reg enum.
|
||||
register: usize,
|
||||
/// The value is in memory at a hard-coded address.
|
||||
memory: u64,
|
||||
/// The value is one of the stack variables.
|
||||
stack_offset: u64,
|
||||
/// The value is in the compare flags assuming an unsigned operation,
|
||||
/// with this operator applied on top of it.
|
||||
compare_flags_unsigned: std.math.CompareOperator,
|
||||
/// The value is in the compare flags assuming a signed operation,
|
||||
/// with this operator applied on top of it.
|
||||
compare_flags_signed: std.math.CompareOperator,
|
||||
|
||||
fn isMemory(mcv: MCValue) bool {
|
||||
return switch (mcv) {
|
||||
.embedded_in_code, .memory, .stack_offset => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn isImmediate(mcv: MCValue) bool {
|
||||
return switch (mcv) {
|
||||
.immediate => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn isMutable(mcv: MCValue) bool {
|
||||
return switch (mcv) {
|
||||
.none => unreachable,
|
||||
.unreach => unreachable,
|
||||
.dead => unreachable,
|
||||
|
||||
.immediate,
|
||||
.embedded_in_code,
|
||||
.memory,
|
||||
.compare_flags_unsigned,
|
||||
.compare_flags_signed,
|
||||
=> false,
|
||||
|
||||
.register,
|
||||
.stack_offset,
|
||||
=> true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn gen(self: *Function) !void {
|
||||
switch (self.target.cpu.arch) {
|
||||
.arm => return self.genArch(.arm),
|
||||
@@ -400,7 +354,28 @@ const Function = struct {
|
||||
}
|
||||
|
||||
fn genArch(self: *Function, comptime arch: std.Target.Cpu.Arch) !void {
|
||||
return self.genBody(self.mod_fn.analysis.success, arch);
|
||||
try self.code.ensureCapacity(self.code.items.len + 11);
|
||||
|
||||
// push rbp
|
||||
// mov rbp, rsp
|
||||
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x55, 0x48, 0x89, 0xe5 });
|
||||
|
||||
// sub rsp, x
|
||||
const stack_end = self.branch_stack.items[0].max_end_stack;
|
||||
if (stack_end > std.math.maxInt(i32)) {
|
||||
return self.fail(self.src, "too much stack used in call parameters", .{});
|
||||
} else if (stack_end > std.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, arch);
|
||||
}
|
||||
|
||||
fn genBody(self: *Function, body: ir.Body, comptime arch: std.Target.Cpu.Arch) InnerError!void {
|
||||
@@ -593,13 +568,42 @@ const Function = struct {
|
||||
}
|
||||
|
||||
fn genCall(self: *Function, inst: *ir.Inst.Call, comptime arch: std.Target.Cpu.Arch) !MCValue {
|
||||
switch (arch) {
|
||||
.x86_64, .i386 => {
|
||||
if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| {
|
||||
if (inst.args.args.len != 0) {
|
||||
return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{});
|
||||
}
|
||||
const fn_ty = inst.args.func.ty;
|
||||
const cc = fn_ty.fnCallingConvention();
|
||||
const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
|
||||
defer self.gpa.free(param_types);
|
||||
fn_ty.fnParamTypes(param_types);
|
||||
var mc_args = try self.gpa.alloc(MCValue, param_types.len);
|
||||
defer self.gpa.free(mc_args);
|
||||
const stack_byte_count = try self.resolveParameters(inst.base.src, cc, param_types, mc_args);
|
||||
|
||||
switch (arch) {
|
||||
.x86_64 => {
|
||||
for (mc_args) |mc_arg, arg_i| {
|
||||
const arg = inst.args.args[arg_i];
|
||||
const arg_mcv = try self.resolveInst(inst.args.args[arg_i]);
|
||||
switch (mc_arg) {
|
||||
.none => continue,
|
||||
.register => |reg| {
|
||||
try self.genSetReg(arg.src, arch, @intToEnum(Reg(arch), @intCast(u8, reg)), arg_mcv);
|
||||
// TODO interact with the register allocator to mark the instruction as moved.
|
||||
},
|
||||
.stack_offset => {
|
||||
// Here we need to emit instructions like this:
|
||||
// mov qword ptr [rsp + stack_offset], x
|
||||
return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
|
||||
},
|
||||
.immediate => unreachable,
|
||||
.unreach => unreachable,
|
||||
.dead => unreachable,
|
||||
.embedded_in_code => unreachable,
|
||||
.memory => unreachable,
|
||||
.compare_flags_signed => unreachable,
|
||||
.compare_flags_unsigned => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| {
|
||||
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
|
||||
const func = func_val.func;
|
||||
const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];
|
||||
@@ -607,17 +611,11 @@ const Function = struct {
|
||||
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
|
||||
const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.offset_table_index * ptr_bytes);
|
||||
// ff 14 25 xx xx xx xx call [addr]
|
||||
try self.code.resize(self.code.items.len + 7);
|
||||
self.code.items[self.code.items.len - 7 ..][0..3].* = [3]u8{ 0xff, 0x14, 0x25 };
|
||||
mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..4], got_addr);
|
||||
const return_type = func.owner_decl.typed_value.most_recent.typed_value.ty.fnReturnType();
|
||||
switch (return_type.zigTypeTag()) {
|
||||
.Void => return MCValue{ .none = {} },
|
||||
.NoReturn => return MCValue{ .unreach = {} },
|
||||
else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}),
|
||||
}
|
||||
try self.code.ensureCapacity(self.code.items.len + 7);
|
||||
self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 });
|
||||
mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), got_addr);
|
||||
} else {
|
||||
return self.fail(inst.base.src, "TODO implement calling weird function values", .{});
|
||||
return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
|
||||
}
|
||||
} else {
|
||||
return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
|
||||
@@ -625,6 +623,13 @@ const Function = struct {
|
||||
},
|
||||
else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),
|
||||
}
|
||||
|
||||
const return_type = fn_ty.fnReturnType();
|
||||
switch (return_type.zigTypeTag()) {
|
||||
.Void => return MCValue{ .none = {} },
|
||||
.NoReturn => return MCValue{ .unreach = {} },
|
||||
else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}),
|
||||
}
|
||||
}
|
||||
|
||||
fn ret(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, mcv: MCValue) !MCValue {
|
||||
@@ -632,9 +637,15 @@ const Function = struct {
|
||||
return self.fail(src, "TODO implement return with non-void operand", .{});
|
||||
}
|
||||
switch (arch) {
|
||||
.i386, .x86_64 => {
|
||||
.i386 => {
|
||||
try self.code.append(0xc3); // ret
|
||||
},
|
||||
.x86_64 => {
|
||||
try self.code.appendSlice(&[_]u8{
|
||||
0x5d, // pop rbp
|
||||
0xc3, // ret
|
||||
});
|
||||
},
|
||||
else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}),
|
||||
}
|
||||
return .unreach;
|
||||
@@ -769,14 +780,22 @@ const Function = struct {
|
||||
}
|
||||
|
||||
fn genBr(self: *Function, inst: *ir.Inst.Br, comptime arch: std.Target.Cpu.Arch) !MCValue {
|
||||
if (!inst.args.operand.ty.hasCodeGenBits())
|
||||
return self.brVoid(inst.base.src, inst.args.block, arch);
|
||||
|
||||
const operand = try self.resolveInst(inst.args.operand);
|
||||
switch (arch) {
|
||||
else => return self.fail(inst.base.src, "TODO implement br for {}", .{self.target.cpu.arch}),
|
||||
}
|
||||
}
|
||||
|
||||
fn genBrVoid(self: *Function, inst: *ir.Inst.BrVoid, comptime arch: std.Target.Cpu.Arch) !MCValue {
|
||||
return self.brVoid(inst.base.src, inst.args.block, arch);
|
||||
}
|
||||
|
||||
fn brVoid(self: *Function, src: usize, block: *ir.Inst.Block, comptime arch: std.Target.Cpu.Arch) !MCValue {
|
||||
// Emit a jump with a relocation. It will be patched up after the block ends.
|
||||
try inst.args.block.codegen.relocs.ensureCapacity(self.gpa, inst.args.block.codegen.relocs.items.len + 1);
|
||||
try block.codegen.relocs.ensureCapacity(self.gpa, block.codegen.relocs.items.len + 1);
|
||||
|
||||
switch (arch) {
|
||||
.i386, .x86_64 => {
|
||||
@@ -785,9 +804,9 @@ const Function = struct {
|
||||
try self.code.resize(self.code.items.len + 5);
|
||||
self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32
|
||||
// Leave the jump offset undefined
|
||||
inst.args.block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 });
|
||||
block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 });
|
||||
},
|
||||
else => return self.fail(inst.base.src, "TODO implement brvoid for {}", .{self.target.cpu.arch}),
|
||||
else => return self.fail(src, "TODO implement brvoid for {}", .{self.target.cpu.arch}),
|
||||
}
|
||||
return .none;
|
||||
}
|
||||
@@ -1122,6 +1141,48 @@ const Function = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn resolveParameters(
|
||||
self: *Function,
|
||||
src: usize,
|
||||
cc: std.builtin.CallingConvention,
|
||||
param_types: []const Type,
|
||||
results: []MCValue,
|
||||
) !u32 {
|
||||
switch (self.target.cpu.arch) {
|
||||
.x86_64 => {
|
||||
switch (cc) {
|
||||
.Naked => {
|
||||
assert(results.len == 0);
|
||||
return 0;
|
||||
},
|
||||
.Unspecified, .C => {
|
||||
var next_int_reg: usize = 0;
|
||||
var next_stack_offset: u32 = 0;
|
||||
|
||||
const integer_registers = [_]Reg(.x86_64){ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
|
||||
for (param_types) |ty, i| {
|
||||
switch (ty.zigTypeTag()) {
|
||||
.Bool, .Int => {
|
||||
if (next_int_reg >= integer_registers.len) {
|
||||
results[i] = .{ .stack_offset = next_stack_offset };
|
||||
next_stack_offset += @intCast(u32, ty.abiSize(self.target.*));
|
||||
} else {
|
||||
results[i] = .{ .register = @enumToInt(integer_registers[next_int_reg]) };
|
||||
next_int_reg += 1;
|
||||
}
|
||||
},
|
||||
else => return self.fail(src, "TODO implement function parameters of type {}", .{@tagName(ty.zigTypeTag())}),
|
||||
}
|
||||
}
|
||||
return next_stack_offset;
|
||||
},
|
||||
else => return self.fail(src, "TODO implement function parameters for {}", .{cc}),
|
||||
}
|
||||
},
|
||||
else => return self.fail(src, "TODO implement C ABI support for {}", .{self.target.cpu.arch}),
|
||||
}
|
||||
}
|
||||
|
||||
fn fail(self: *Function, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } {
|
||||
@setCold(true);
|
||||
assert(self.err_msg == null);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
const link = @import("link.zig");
|
||||
const Module = @import("Module.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const Inst = @import("ir.zig").Inst;
|
||||
const Value = @import("value.zig").Value;
|
||||
const Type = @import("type.zig").Type;
|
||||
const link = @import("../link.zig");
|
||||
const Module = @import("../Module.zig");
|
||||
|
||||
const Inst = @import("../ir.zig").Inst;
|
||||
const Value = @import("../value.zig").Value;
|
||||
const Type = @import("../type.zig").Type;
|
||||
|
||||
const C = link.File.C;
|
||||
const Decl = Module.Decl;
|
||||
@@ -95,7 +95,8 @@ fn genFn(file: *C, decl: *Decl) !void {
|
||||
.assembly => try genAsm(file, inst.cast(Inst.Assembly).?, decl),
|
||||
.call => try genCall(file, inst.cast(Inst.Call).?, decl),
|
||||
.ret => try genRet(file, inst.cast(Inst.Ret).?, decl, tv.ty.fnReturnType()),
|
||||
else => |e| return file.fail(decl.src(), "TODO {}", .{e}),
|
||||
.retvoid => try file.main.writer().print("return;", .{}),
|
||||
else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}),
|
||||
}
|
||||
}
|
||||
try writer.writeAll("\n");
|
||||
@@ -60,36 +60,6 @@ pub const Inst = struct {
|
||||
retvoid,
|
||||
sub,
|
||||
unreach,
|
||||
|
||||
/// Returns whether the instruction is one of the control flow "noreturn" types.
|
||||
/// Function calls do not count. When ZIR is generated, the compiler automatically
|
||||
/// emits an `Unreach` after a function call with the `noreturn` return type.
|
||||
pub fn isNoReturn(tag: Tag) bool {
|
||||
return switch (tag) {
|
||||
.add,
|
||||
.arg,
|
||||
.assembly,
|
||||
.bitcast,
|
||||
.block,
|
||||
.breakpoint,
|
||||
.call,
|
||||
.cmp,
|
||||
.constant,
|
||||
.isnonnull,
|
||||
.isnull,
|
||||
.ptrtoint,
|
||||
.sub,
|
||||
=> false,
|
||||
|
||||
.br,
|
||||
.brvoid,
|
||||
.condbr,
|
||||
.ret,
|
||||
.retvoid,
|
||||
.unreach,
|
||||
=> true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn cast(base: *Inst, comptime T: type) ?*T {
|
||||
|
||||
@@ -7,7 +7,7 @@ const Module = @import("Module.zig");
|
||||
const fs = std.fs;
|
||||
const elf = std.elf;
|
||||
const codegen = @import("codegen.zig");
|
||||
const cgen = @import("cgen.zig");
|
||||
const c_codegen = @import("codegen/c.zig");
|
||||
|
||||
const default_entry_addr = 0x8000000;
|
||||
|
||||
@@ -259,7 +259,7 @@ pub const File = struct {
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void {
|
||||
cgen.generate(self, decl) catch |err| {
|
||||
c_codegen.generate(self, decl) catch |err| {
|
||||
if (err == error.CGenFailure) {
|
||||
try module.failed_decls.put(module.gpa, decl, self.error_msg);
|
||||
}
|
||||
|
||||
@@ -468,6 +468,10 @@ pub const Type = extern union {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isNoReturn(self: Type) bool {
|
||||
return self.zigTypeTag() == .NoReturn;
|
||||
}
|
||||
|
||||
/// Asserts that hasCodeGenBits() is true.
|
||||
pub fn abiAlignment(self: Type, target: Target) u32 {
|
||||
return switch (self.tag()) {
|
||||
|
||||
@@ -81,6 +81,52 @@ pub const Inst = struct {
|
||||
condbr,
|
||||
isnull,
|
||||
isnonnull,
|
||||
|
||||
/// Returns whether the instruction is one of the control flow "noreturn" types.
|
||||
/// Function calls do not count.
|
||||
pub fn isNoReturn(tag: Tag) bool {
|
||||
return switch (tag) {
|
||||
.arg,
|
||||
.block,
|
||||
.breakpoint,
|
||||
.call,
|
||||
.@"const",
|
||||
.declref,
|
||||
.declref_str,
|
||||
.declval,
|
||||
.declval_in_module,
|
||||
.str,
|
||||
.int,
|
||||
.inttype,
|
||||
.ptrtoint,
|
||||
.fieldptr,
|
||||
.deref,
|
||||
.as,
|
||||
.@"asm",
|
||||
.@"fn",
|
||||
.fntype,
|
||||
.@"export",
|
||||
.primitive,
|
||||
.intcast,
|
||||
.bitcast,
|
||||
.elemptr,
|
||||
.add,
|
||||
.sub,
|
||||
.cmp,
|
||||
.isnull,
|
||||
.isnonnull,
|
||||
=> false,
|
||||
|
||||
.condbr,
|
||||
.@"unreachable",
|
||||
.@"return",
|
||||
.returnvoid,
|
||||
.@"break",
|
||||
.breakvoid,
|
||||
.compileerror,
|
||||
=> true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn TagToType(tag: Tag) type {
|
||||
|
||||
@@ -62,6 +62,7 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
\\ register size_t rax_constant __asm__("rax") = 231;
|
||||
\\ register size_t rdi_constant __asm__("rdi") = 0;
|
||||
\\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant));
|
||||
\\ return;
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
|
||||
@@ -120,7 +120,7 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
}
|
||||
|
||||
{
|
||||
var case = ctx.exe("adding numbers", linux_x64);
|
||||
var case = ctx.exe("adding numbers at comptime", linux_x64);
|
||||
case.addCompareOutput(
|
||||
\\export fn _start() noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
@@ -143,4 +143,31 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
"Hello, World!\n",
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
var case = ctx.exe("adding numbers at runtime", linux_x64);
|
||||
case.addCompareOutput(
|
||||
\\export fn _start() noreturn {
|
||||
\\ add(3, 4);
|
||||
\\
|
||||
\\ exit();
|
||||
\\}
|
||||
\\
|
||||
\\fn add(a: u32, b: u32) void {
|
||||
\\ if (a + b != 7) unreachable;
|
||||
\\}
|
||||
\\
|
||||
\\fn exit() noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
\\ :
|
||||
\\ : [number] "{rax}" (231),
|
||||
\\ [arg1] "{rdi}" (0)
|
||||
\\ : "rcx", "r11", "memory"
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
,
|
||||
"",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user