zig/src-self-hosted/codegen.zig

195 lines
7.7 KiB
Zig

const std = @import("std");
const mem = std.mem;
const assert = std.debug.assert;
const ir = @import("ir.zig");
const Type = @import("type.zig").Type;
const Value = @import("value.zig").Value;
pub const ErrorMsg = struct {
byte_offset: usize,
msg: []const u8,
};
pub const Symbol = struct {
errors: []ErrorMsg,
pub fn deinit(self: *Symbol, allocator: *mem.Allocator) void {
for (self.errors) |err| {
allocator.free(err.msg);
}
allocator.free(self.errors);
self.* = undefined;
}
};
pub fn generateSymbol(typed_value: ir.TypedValue, module: ir.Module, code: *std.ArrayList(u8)) !Symbol {
switch (typed_value.ty.zigTypeTag()) {
.Fn => {
const index = typed_value.val.cast(Value.Payload.Function).?.index;
const module_fn = module.fns[index];
var function = Function{
.module = &module,
.mod_fn = &module_fn,
.code = code,
.inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(code.allocator),
.errors = std.ArrayList(ErrorMsg).init(code.allocator),
};
defer function.inst_table.deinit();
defer function.errors.deinit();
for (module_fn.body) |inst| {
const new_inst = function.genFuncInst(inst) catch |err| switch (err) {
error.CodegenFail => {
assert(function.errors.items.len != 0);
break;
},
else => |e| return e,
};
try function.inst_table.putNoClobber(inst, new_inst);
}
return Symbol{ .errors = function.errors.toOwnedSlice() };
},
else => @panic("TODO implement generateSymbol for non-function types"),
}
}
const Function = struct {
module: *const ir.Module,
mod_fn: *const ir.Module.Fn,
code: *std.ArrayList(u8),
inst_table: std.AutoHashMap(*ir.Inst, MCValue),
errors: std.ArrayList(ErrorMsg),
const MCValue = union(enum) {
none,
unreach,
/// 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,
};
fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue {
switch (inst.tag) {
.unreach => return self.genPanic(inst.src),
.constant => unreachable, // excluded from function bodies
.assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?),
.ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?),
}
}
fn genPanic(self: *Function, src: usize) !MCValue {
// TODO change this to call the panic function
switch (self.module.target.cpu.arch) {
.i386, .x86_64 => {
try self.code.append(0xcc); // int3
},
else => return self.fail(src, "TODO implement panic for {}", .{self.module.target.cpu.arch}),
}
return .unreach;
}
fn genRet(self: *Function, src: usize) !void {
// TODO change this to call the panic function
switch (self.module.target.cpu.arch) {
.i386, .x86_64 => {
try self.code.append(0xc3); // ret
},
else => return self.fail(src, "TODO implement ret for {}", .{self.module.target.cpu.arch}),
}
}
fn genRelativeFwdJump(self: *Function, src: usize, amount: u32) !void {
switch (self.module.target.cpu.arch) {
.i386, .x86_64 => {
if (amount <= std.math.maxInt(u8)) {
try self.code.resize(self.code.items.len + 2);
self.code.items[self.code.items.len - 2] = 0xeb;
self.code.items[self.code.items.len - 1] = @intCast(u8, amount);
} else if (amount <= std.math.maxInt(u16)) {
try self.code.resize(self.code.items.len + 3);
self.code.items[self.code.items.len - 3] = 0xe9; // jmp rel16
const imm_ptr = self.code.items[self.code.items.len - 2 ..][0..2];
mem.writeIntLittle(u16, imm_ptr, @intCast(u16, amount));
} else {
try self.code.resize(self.code.items.len + 5);
self.code.items[self.code.items.len - 5] = 0xea; // jmp rel32
const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
mem.writeIntLittle(u32, imm_ptr, amount);
}
},
else => return self.fail(src, "TODO implement relative forward jump for {}", .{self.module.target.cpu.arch}),
}
}
fn genAsm(self: *Function, inst: *ir.Inst.Assembly) !MCValue {
return self.fail(inst.base.src, "TODO machine code gen assembly", .{});
}
fn genPtrToInt(self: *Function, inst: *ir.Inst.PtrToInt) !MCValue {
// no-op
return self.resolveInst(inst.args.ptr);
}
fn resolveInst(self: *Function, inst: *ir.Inst) !MCValue {
if (self.inst_table.getValue(inst)) |mcv| {
return mcv;
}
if (inst.cast(ir.Inst.Constant)) |const_inst| {
const mcvalue = try self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val });
try self.inst_table.putNoClobber(inst, mcvalue);
return mcvalue;
} else {
return self.inst_table.getValue(inst).?;
}
}
fn genTypedValue(self: *Function, src: usize, typed_value: ir.TypedValue) !MCValue {
switch (typed_value.ty.zigTypeTag()) {
.Pointer => {
const ptr_elem_type = typed_value.ty.elemType();
switch (ptr_elem_type.zigTypeTag()) {
.Array => {
// TODO more checks to make sure this can be emitted as a string literal
const bytes = try typed_value.val.toAllocatedBytes(self.code.allocator);
defer self.code.allocator.free(bytes);
const smaller_len = std.math.cast(u32, bytes.len) catch
return self.fail(src, "TODO handle a larger string constant", .{});
// Emit the string literal directly into the code; jump over it.
const offset = self.code.items.len;
try self.genRelativeFwdJump(src, smaller_len);
try self.code.appendSlice(bytes);
return MCValue{ .embedded_in_code = offset };
},
else => |t| return self.fail(src, "TODO implement emitTypedValue for pointer to '{}'", .{@tagName(t)}),
}
},
.Int => {
const info = typed_value.ty.intInfo(self.module.target);
const ptr_bits = self.module.target.cpu.arch.ptrBitWidth();
if (info.bits > ptr_bits or info.signed) {
return self.fail(src, "TODO const int bigger than ptr and signed int", .{});
}
return MCValue{ .immediate = typed_value.val.toUnsignedInt() };
},
else => return self.fail(src, "TODO implement const of type '{}'", .{typed_value.ty}),
}
}
fn fail(self: *Function, src: usize, comptime format: []const u8, args: var) error{ CodegenFail, OutOfMemory } {
@setCold(true);
const msg = try std.fmt.allocPrint(self.errors.allocator, format, args);
{
errdefer self.errors.allocator.free(msg);
(try self.errors.addOne()).* = .{
.byte_offset = src,
.msg = msg,
};
}
return error.CodegenFail;
}
};