zig/src-self-hosted/zir.zig
Andrew Kelley 130c7fd23b self-hosted: working towards conditional branching test case
New features:
 * Functions can have parameters in semantic analysis. Codegen
   is not implemented yet.
 * Support for i8, i16, i32, i64, u8, u16, u32, u64 primitive
   identifiers.
 * New ZIR instructions: arg, block, and breakvoid

Implementation details:

 * Move Module.Body to ir.Body
 * Scope.Block gains a parent field and an optional Label field
 * Fix bug in integer type equality comparison.

Here's the test case I'm working towards:

```
@void = primitive(void)
@i32 = primitive(i32)
@fnty = fntype([@i32, @i32], @void)

@0 = str("entry")
@1 = export(@0, "entry")

@entry = fn(@fnty, {
  %0 = arg(0)
  %1 = arg(1)
  %2 = add(%0, %1)
  %3 = int(7)
  %4 = block("if", {
    %neq = cmp(%2, neq, %3)
    %5 = condbr(%neq, {
      %6 = unreachable()
    }, {
      %7 = breakvoid("if")
    })
  })
  %11 = returnvoid()
})
```

$ ./zig-cache/bin/zig build-obj test.zir
test.zir:9:12: error: TODO implement function parameters for Arch.x86_64

That's where I left off.
2020-06-26 02:30:14 -04:00

1895 lines
71 KiB
Zig

//! This file has to do with parsing and rendering the ZIR text format.
const std = @import("std");
const mem = std.mem;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
const Type = @import("type.zig").Type;
const Value = @import("value.zig").Value;
const TypedValue = @import("TypedValue.zig");
const ir = @import("ir.zig");
const IrModule = @import("Module.zig");
/// This struct is relevent only for the ZIR Module text format. It is not used for
/// semantic analysis of Zig source code.
pub const Decl = struct {
name: []const u8,
/// Hash of slice into the source of the part after the = and before the next instruction.
contents_hash: std.zig.SrcHash,
inst: *Inst,
};
/// These are instructions that correspond to the ZIR text format. See `ir.Inst` for
/// in-memory, analyzed instructions with types and values.
pub const Inst = struct {
tag: Tag,
/// Byte offset into the source.
src: usize,
/// Pre-allocated field for mapping ZIR text instructions to post-analysis instructions.
analyzed_inst: ?*ir.Inst = null,
/// These names are used directly as the instruction names in the text format.
pub const Tag = enum {
/// Function parameter value.
arg,
/// A labeled block of code, which can return a value.
block,
breakpoint,
/// Same as `break` but without an operand; the operand is assumed to be the void value.
breakvoid,
call,
compileerror,
/// Special case, has no textual representation.
@"const",
/// Represents a pointer to a global decl by name.
declref,
/// Represents a pointer to a global decl by string name.
declref_str,
/// The syntax `@foo` is equivalent to `declval("foo")`.
/// declval is equivalent to declref followed by deref.
declval,
/// Same as declval but the parameter is a `*Module.Decl` rather than a name.
declval_in_module,
/// String Literal. Makes an anonymous Decl and then takes a pointer to it.
str,
int,
ptrtoint,
fieldptr,
deref,
as,
@"asm",
@"unreachable",
@"return",
returnvoid,
@"fn",
fntype,
@"export",
primitive,
intcast,
bitcast,
elemptr,
add,
cmp,
condbr,
isnull,
isnonnull,
};
pub fn TagToType(tag: Tag) type {
return switch (tag) {
.arg => Arg,
.block => Block,
.breakpoint => Breakpoint,
.breakvoid => BreakVoid,
.call => Call,
.declref => DeclRef,
.declref_str => DeclRefStr,
.declval => DeclVal,
.declval_in_module => DeclValInModule,
.compileerror => CompileError,
.@"const" => Const,
.str => Str,
.int => Int,
.ptrtoint => PtrToInt,
.fieldptr => FieldPtr,
.deref => Deref,
.as => As,
.@"asm" => Asm,
.@"unreachable" => Unreachable,
.@"return" => Return,
.returnvoid => ReturnVoid,
.@"fn" => Fn,
.@"export" => Export,
.primitive => Primitive,
.fntype => FnType,
.intcast => IntCast,
.bitcast => BitCast,
.elemptr => ElemPtr,
.add => Add,
.cmp => Cmp,
.condbr => CondBr,
.isnull => IsNull,
.isnonnull => IsNonNull,
};
}
pub fn cast(base: *Inst, comptime T: type) ?*T {
if (base.tag != T.base_tag)
return null;
return @fieldParentPtr(T, "base", base);
}
pub const Arg = struct {
pub const base_tag = Tag.arg;
base: Inst,
positionals: struct {
index: usize,
},
kw_args: struct {},
};
pub const Block = struct {
pub const base_tag = Tag.block;
base: Inst,
positionals: struct {
label: []const u8,
body: Module.Body,
},
kw_args: struct {},
};
pub const Breakpoint = struct {
pub const base_tag = Tag.breakpoint;
base: Inst,
positionals: struct {},
kw_args: struct {},
};
pub const BreakVoid = struct {
pub const base_tag = Tag.breakvoid;
base: Inst,
positionals: struct {
label: []const u8,
},
kw_args: struct {},
};
pub const Call = struct {
pub const base_tag = Tag.call;
base: Inst,
positionals: struct {
func: *Inst,
args: []*Inst,
},
kw_args: struct {
modifier: std.builtin.CallOptions.Modifier = .auto,
},
};
pub const DeclRef = struct {
pub const base_tag = Tag.declref;
base: Inst,
positionals: struct {
name: []const u8,
},
kw_args: struct {},
};
pub const DeclRefStr = struct {
pub const base_tag = Tag.declref_str;
base: Inst,
positionals: struct {
name: *Inst,
},
kw_args: struct {},
};
pub const DeclVal = struct {
pub const base_tag = Tag.declval;
base: Inst,
positionals: struct {
name: []const u8,
},
kw_args: struct {},
};
pub const DeclValInModule = struct {
pub const base_tag = Tag.declval_in_module;
base: Inst,
positionals: struct {
decl: *IrModule.Decl,
},
kw_args: struct {},
};
pub const CompileError = struct {
pub const base_tag = Tag.compileerror;
base: Inst,
positionals: struct {
msg: []const u8,
},
kw_args: struct {},
};
pub const Const = struct {
pub const base_tag = Tag.@"const";
base: Inst,
positionals: struct {
typed_value: TypedValue,
},
kw_args: struct {},
};
pub const Str = struct {
pub const base_tag = Tag.str;
base: Inst,
positionals: struct {
bytes: []const u8,
},
kw_args: struct {},
};
pub const Int = struct {
pub const base_tag = Tag.int;
base: Inst,
positionals: struct {
int: BigIntConst,
},
kw_args: struct {},
};
pub const PtrToInt = struct {
pub const builtin_name = "@ptrToInt";
pub const base_tag = Tag.ptrtoint;
base: Inst,
positionals: struct {
ptr: *Inst,
},
kw_args: struct {},
};
pub const FieldPtr = struct {
pub const base_tag = Tag.fieldptr;
base: Inst,
positionals: struct {
object_ptr: *Inst,
field_name: *Inst,
},
kw_args: struct {},
};
pub const Deref = struct {
pub const base_tag = Tag.deref;
base: Inst,
positionals: struct {
ptr: *Inst,
},
kw_args: struct {},
};
pub const As = struct {
pub const base_tag = Tag.as;
base: Inst,
positionals: struct {
dest_type: *Inst,
value: *Inst,
},
kw_args: struct {},
};
pub const Asm = struct {
pub const base_tag = Tag.@"asm";
base: Inst,
positionals: struct {
asm_source: *Inst,
return_type: *Inst,
},
kw_args: struct {
@"volatile": bool = false,
output: ?*Inst = null,
inputs: []*Inst = &[0]*Inst{},
clobbers: []*Inst = &[0]*Inst{},
args: []*Inst = &[0]*Inst{},
},
};
pub const Unreachable = struct {
pub const base_tag = Tag.@"unreachable";
base: Inst,
positionals: struct {},
kw_args: struct {},
};
pub const Return = struct {
pub const base_tag = Tag.@"return";
base: Inst,
positionals: struct {
operand: *Inst,
},
kw_args: struct {},
};
pub const ReturnVoid = struct {
pub const base_tag = Tag.returnvoid;
base: Inst,
positionals: struct {},
kw_args: struct {},
};
pub const Fn = struct {
pub const base_tag = Tag.@"fn";
base: Inst,
positionals: struct {
fn_type: *Inst,
body: Module.Body,
},
kw_args: struct {},
};
pub const FnType = struct {
pub const base_tag = Tag.fntype;
base: Inst,
positionals: struct {
param_types: []*Inst,
return_type: *Inst,
},
kw_args: struct {
cc: std.builtin.CallingConvention = .Unspecified,
},
};
pub const Export = struct {
pub const base_tag = Tag.@"export";
base: Inst,
positionals: struct {
symbol_name: *Inst,
decl_name: []const u8,
},
kw_args: struct {},
};
pub const Primitive = struct {
pub const base_tag = Tag.primitive;
base: Inst,
positionals: struct {
tag: Builtin,
},
kw_args: struct {},
pub const Builtin = enum {
i8,
u8,
i16,
u16,
i32,
u32,
i64,
u64,
isize,
usize,
c_short,
c_ushort,
c_int,
c_uint,
c_long,
c_ulong,
c_longlong,
c_ulonglong,
c_longdouble,
c_void,
f16,
f32,
f64,
f128,
bool,
void,
noreturn,
type,
anyerror,
comptime_int,
comptime_float,
@"true",
@"false",
@"null",
@"undefined",
void_value,
pub fn toTypedValue(self: Builtin) TypedValue {
return switch (self) {
.i8 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.i8_type) },
.u8 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.u8_type) },
.i16 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.i16_type) },
.u16 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.u16_type) },
.i32 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.i32_type) },
.u32 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.u32_type) },
.i64 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.i64_type) },
.u64 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.u64_type) },
.isize => .{ .ty = Type.initTag(.type), .val = Value.initTag(.isize_type) },
.usize => .{ .ty = Type.initTag(.type), .val = Value.initTag(.usize_type) },
.c_short => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_short_type) },
.c_ushort => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_ushort_type) },
.c_int => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_int_type) },
.c_uint => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_uint_type) },
.c_long => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_long_type) },
.c_ulong => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_ulong_type) },
.c_longlong => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_longlong_type) },
.c_ulonglong => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_ulonglong_type) },
.c_longdouble => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_longdouble_type) },
.c_void => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_void_type) },
.f16 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.f16_type) },
.f32 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.f32_type) },
.f64 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.f64_type) },
.f128 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.f128_type) },
.bool => .{ .ty = Type.initTag(.type), .val = Value.initTag(.bool_type) },
.void => .{ .ty = Type.initTag(.type), .val = Value.initTag(.void_type) },
.noreturn => .{ .ty = Type.initTag(.type), .val = Value.initTag(.noreturn_type) },
.type => .{ .ty = Type.initTag(.type), .val = Value.initTag(.type_type) },
.anyerror => .{ .ty = Type.initTag(.type), .val = Value.initTag(.anyerror_type) },
.comptime_int => .{ .ty = Type.initTag(.type), .val = Value.initTag(.comptime_int_type) },
.comptime_float => .{ .ty = Type.initTag(.type), .val = Value.initTag(.comptime_float_type) },
.@"true" => .{ .ty = Type.initTag(.bool), .val = Value.initTag(.bool_true) },
.@"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) },
};
}
};
};
pub const IntCast = struct {
pub const base_tag = Tag.intcast;
base: Inst,
positionals: struct {
dest_type: *Inst,
value: *Inst,
},
kw_args: struct {},
};
pub const BitCast = struct {
pub const base_tag = Tag.bitcast;
base: Inst,
positionals: struct {
dest_type: *Inst,
operand: *Inst,
},
kw_args: struct {},
};
pub const ElemPtr = struct {
pub const base_tag = Tag.elemptr;
base: Inst,
positionals: struct {
array_ptr: *Inst,
index: *Inst,
},
kw_args: struct {},
};
pub const Add = struct {
pub const base_tag = Tag.add;
base: Inst,
positionals: struct {
lhs: *Inst,
rhs: *Inst,
},
kw_args: struct {},
};
pub const Cmp = struct {
pub const base_tag = Tag.cmp;
base: Inst,
positionals: struct {
lhs: *Inst,
op: std.math.CompareOperator,
rhs: *Inst,
},
kw_args: struct {},
};
pub const CondBr = struct {
pub const base_tag = Tag.condbr;
base: Inst,
positionals: struct {
condition: *Inst,
true_body: Module.Body,
false_body: Module.Body,
},
kw_args: struct {},
};
pub const IsNull = struct {
pub const base_tag = Tag.isnull;
base: Inst,
positionals: struct {
operand: *Inst,
},
kw_args: struct {},
};
pub const IsNonNull = struct {
pub const base_tag = Tag.isnonnull;
base: Inst,
positionals: struct {
operand: *Inst,
},
kw_args: struct {},
};
};
pub const ErrorMsg = struct {
byte_offset: usize,
msg: []const u8,
};
pub const Module = struct {
decls: []*Decl,
arena: std.heap.ArenaAllocator,
error_msg: ?ErrorMsg = null,
pub const Body = struct {
instructions: []*Inst,
};
pub fn deinit(self: *Module, allocator: *Allocator) void {
allocator.free(self.decls);
self.arena.deinit();
self.* = undefined;
}
/// This is a debugging utility for rendering the tree to stderr.
pub fn dump(self: Module) void {
self.writeToStream(std.heap.page_allocator, std.io.getStdErr().outStream()) catch {};
}
const InstPtrTable = std.AutoHashMap(*Inst, struct { inst: *Inst, index: ?usize, name: []const u8 });
const DeclAndIndex = struct {
decl: *Decl,
index: usize,
};
/// TODO Look into making a table to speed this up.
pub fn findDecl(self: Module, name: []const u8) ?DeclAndIndex {
for (self.decls) |decl, i| {
if (mem.eql(u8, decl.name, name)) {
return DeclAndIndex{
.decl = decl,
.index = i,
};
}
}
return null;
}
pub fn findInstDecl(self: Module, inst: *Inst) ?DeclAndIndex {
for (self.decls) |decl, i| {
if (decl.inst == inst) {
return DeclAndIndex{
.decl = decl,
.index = i,
};
}
}
return null;
}
/// The allocator is used for temporary storage, but this function always returns
/// with no resources allocated.
pub fn writeToStream(self: Module, allocator: *Allocator, stream: var) !void {
// First, build a map of *Inst to @ or % indexes
var inst_table = InstPtrTable.init(allocator);
defer inst_table.deinit();
try inst_table.ensureCapacity(self.decls.len);
for (self.decls) |decl, decl_i| {
try inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name });
if (decl.inst.cast(Inst.Fn)) |fn_inst| {
for (fn_inst.positionals.body.instructions) |inst, inst_i| {
try inst_table.putNoClobber(inst, .{ .inst = inst, .index = inst_i, .name = undefined });
}
}
}
for (self.decls) |decl, i| {
try stream.print("@{} ", .{decl.name});
try self.writeInstToStream(stream, decl.inst, &inst_table);
try stream.writeByte('\n');
}
}
fn writeInstToStream(
self: Module,
stream: var,
inst: *Inst,
inst_table: *const InstPtrTable,
) @TypeOf(stream).Error!void {
// TODO I tried implementing this with an inline for loop and hit a compiler bug
switch (inst.tag) {
.arg => return self.writeInstToStreamGeneric(stream, .arg, inst, inst_table),
.block => return self.writeInstToStreamGeneric(stream, .block, inst, inst_table),
.breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, inst, inst_table),
.breakvoid => return self.writeInstToStreamGeneric(stream, .breakvoid, inst, inst_table),
.call => return self.writeInstToStreamGeneric(stream, .call, inst, inst_table),
.declref => return self.writeInstToStreamGeneric(stream, .declref, inst, inst_table),
.declref_str => return self.writeInstToStreamGeneric(stream, .declref_str, inst, inst_table),
.declval => return self.writeInstToStreamGeneric(stream, .declval, inst, inst_table),
.declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, inst, inst_table),
.compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, inst, inst_table),
.@"const" => return self.writeInstToStreamGeneric(stream, .@"const", inst, inst_table),
.str => return self.writeInstToStreamGeneric(stream, .str, inst, inst_table),
.int => return self.writeInstToStreamGeneric(stream, .int, inst, inst_table),
.ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, inst, inst_table),
.fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, inst, inst_table),
.deref => return self.writeInstToStreamGeneric(stream, .deref, inst, inst_table),
.as => return self.writeInstToStreamGeneric(stream, .as, inst, inst_table),
.@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", inst, inst_table),
.@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", inst, inst_table),
.@"return" => return self.writeInstToStreamGeneric(stream, .@"return", inst, inst_table),
.returnvoid => return self.writeInstToStreamGeneric(stream, .returnvoid, inst, inst_table),
.@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", inst, inst_table),
.@"export" => return self.writeInstToStreamGeneric(stream, .@"export", inst, inst_table),
.primitive => return self.writeInstToStreamGeneric(stream, .primitive, inst, inst_table),
.fntype => return self.writeInstToStreamGeneric(stream, .fntype, inst, inst_table),
.intcast => return self.writeInstToStreamGeneric(stream, .intcast, inst, inst_table),
.bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, inst, inst_table),
.elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, inst, inst_table),
.add => return self.writeInstToStreamGeneric(stream, .add, inst, inst_table),
.cmp => return self.writeInstToStreamGeneric(stream, .cmp, inst, inst_table),
.condbr => return self.writeInstToStreamGeneric(stream, .condbr, inst, inst_table),
.isnull => return self.writeInstToStreamGeneric(stream, .isnull, inst, inst_table),
.isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, inst, inst_table),
}
}
fn writeInstToStreamGeneric(
self: Module,
stream: var,
comptime inst_tag: Inst.Tag,
base: *Inst,
inst_table: *const InstPtrTable,
) !void {
const SpecificInst = Inst.TagToType(inst_tag);
const inst = @fieldParentPtr(SpecificInst, "base", base);
const Positionals = @TypeOf(inst.positionals);
try stream.writeAll("= " ++ @tagName(inst_tag) ++ "(");
const pos_fields = @typeInfo(Positionals).Struct.fields;
inline for (pos_fields) |arg_field, i| {
if (i != 0) {
try stream.writeAll(", ");
}
try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name), inst_table);
}
comptime var need_comma = pos_fields.len != 0;
const KW_Args = @TypeOf(inst.kw_args);
inline for (@typeInfo(KW_Args).Struct.fields) |arg_field, i| {
if (@typeInfo(arg_field.field_type) == .Optional) {
if (@field(inst.kw_args, arg_field.name)) |non_optional| {
if (need_comma) try stream.writeAll(", ");
try stream.print("{}=", .{arg_field.name});
try self.writeParamToStream(stream, non_optional, inst_table);
need_comma = true;
}
} else {
if (need_comma) try stream.writeAll(", ");
try stream.print("{}=", .{arg_field.name});
try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name), inst_table);
need_comma = true;
}
}
try stream.writeByte(')');
}
fn writeParamToStream(self: Module, stream: var, param: var, inst_table: *const InstPtrTable) !void {
if (@typeInfo(@TypeOf(param)) == .Enum) {
return stream.writeAll(@tagName(param));
}
switch (@TypeOf(param)) {
*Inst => return self.writeInstParamToStream(stream, param, inst_table),
[]*Inst => {
try stream.writeByte('[');
for (param) |inst, i| {
if (i != 0) {
try stream.writeAll(", ");
}
try self.writeInstParamToStream(stream, inst, inst_table);
}
try stream.writeByte(']');
},
Module.Body => {
try stream.writeAll("{\n");
for (param.instructions) |inst, i| {
try stream.print(" %{} ", .{i});
try self.writeInstToStream(stream, inst, inst_table);
try stream.writeByte('\n');
}
try stream.writeByte('}');
},
bool => return stream.writeByte("01"[@boolToInt(param)]),
[]u8, []const u8 => return std.zig.renderStringLiteral(param, stream),
BigIntConst, usize => return stream.print("{}", .{param}),
TypedValue => unreachable, // this is a special case
*IrModule.Decl => unreachable, // this is a special case
else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)),
}
}
fn writeInstParamToStream(self: Module, stream: var, inst: *Inst, inst_table: *const InstPtrTable) !void {
if (inst_table.getValue(inst)) |info| {
if (info.index) |i| {
try stream.print("%{}", .{info.index});
} else {
try stream.print("@{}", .{info.name});
}
} else if (inst.cast(Inst.DeclVal)) |decl_val| {
try stream.print("@{}", .{decl_val.positionals.name});
} else if (inst.cast(Inst.DeclValInModule)) |decl_val| {
try stream.print("@{}", .{decl_val.positionals.decl.name});
} else {
// This should be unreachable in theory, but since ZIR is used for debugging the compiler
// we output some debug text instead.
try stream.print("?{}?", .{@tagName(inst.tag)});
}
}
};
pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module {
var global_name_map = std.StringHashMap(*Inst).init(allocator);
defer global_name_map.deinit();
var parser: Parser = .{
.allocator = allocator,
.arena = std.heap.ArenaAllocator.init(allocator),
.i = 0,
.source = source,
.global_name_map = &global_name_map,
.decls = .{},
.unnamed_index = 0,
};
errdefer parser.arena.deinit();
parser.parseRoot() catch |err| switch (err) {
error.ParseFailure => {
assert(parser.error_msg != null);
},
else => |e| return e,
};
return Module{
.decls = parser.decls.toOwnedSlice(allocator),
.arena = parser.arena,
.error_msg = parser.error_msg,
};
}
const Parser = struct {
allocator: *Allocator,
arena: std.heap.ArenaAllocator,
i: usize,
source: [:0]const u8,
decls: std.ArrayListUnmanaged(*Decl),
global_name_map: *std.StringHashMap(*Inst),
error_msg: ?ErrorMsg = null,
unnamed_index: usize,
const Body = struct {
instructions: std.ArrayList(*Inst),
name_map: *std.StringHashMap(*Inst),
};
fn parseBody(self: *Parser, body_ctx: ?*Body) !Module.Body {
var name_map = std.StringHashMap(*Inst).init(self.allocator);
defer name_map.deinit();
var body_context = Body{
.instructions = std.ArrayList(*Inst).init(self.allocator),
.name_map = if (body_ctx) |bctx| bctx.name_map else &name_map,
};
defer body_context.instructions.deinit();
try requireEatBytes(self, "{");
skipSpace(self);
while (true) : (self.i += 1) switch (self.source[self.i]) {
';' => _ = try skipToAndOver(self, '\n'),
'%' => {
self.i += 1;
const ident = try skipToAndOver(self, ' ');
skipSpace(self);
try requireEatBytes(self, "=");
skipSpace(self);
const decl = try parseInstruction(self, &body_context, ident);
const ident_index = body_context.instructions.items.len;
if (try body_context.name_map.put(ident, decl.inst)) |_| {
return self.fail("redefinition of identifier '{}'", .{ident});
}
try body_context.instructions.append(decl.inst);
continue;
},
' ', '\n' => continue,
'}' => {
self.i += 1;
break;
},
else => |byte| return self.failByte(byte),
};
// Move the instructions to the arena
const instrs = try self.arena.allocator.alloc(*Inst, body_context.instructions.items.len);
mem.copy(*Inst, instrs, body_context.instructions.items);
return Module.Body{ .instructions = instrs };
}
fn parseStringLiteral(self: *Parser) ![]u8 {
const start = self.i;
try self.requireEatBytes("\"");
while (true) : (self.i += 1) switch (self.source[self.i]) {
'"' => {
self.i += 1;
const span = self.source[start..self.i];
var bad_index: usize = undefined;
const parsed = std.zig.parseStringLiteral(&self.arena.allocator, span, &bad_index) catch |err| switch (err) {
error.InvalidCharacter => {
self.i = start + bad_index;
const bad_byte = self.source[self.i];
return self.fail("invalid string literal character: '{c}'\n", .{bad_byte});
},
else => |e| return e,
};
return parsed;
},
'\\' => {
self.i += 1;
continue;
},
0 => return self.failByte(0),
else => continue,
};
}
fn parseIntegerLiteral(self: *Parser) !BigIntConst {
const start = self.i;
if (self.source[self.i] == '-') self.i += 1;
while (true) : (self.i += 1) switch (self.source[self.i]) {
'0'...'9' => continue,
else => break,
};
const number_text = self.source[start..self.i];
const base = 10;
// TODO reuse the same array list for this
const limbs_buffer_len = std.math.big.int.calcSetStringLimbsBufferLen(base, number_text.len);
const limbs_buffer = try self.allocator.alloc(std.math.big.Limb, limbs_buffer_len);
defer self.allocator.free(limbs_buffer);
const limb_len = std.math.big.int.calcSetStringLimbCount(base, number_text.len);
const limbs = try self.arena.allocator.alloc(std.math.big.Limb, limb_len);
var result = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
result.setString(base, number_text, limbs_buffer, self.allocator) catch |err| switch (err) {
error.InvalidCharacter => {
self.i = start;
return self.fail("invalid digit in integer literal", .{});
},
};
return result.toConst();
}
fn parseRoot(self: *Parser) !void {
// The IR format is designed so that it can be tokenized and parsed at the same time.
while (true) {
switch (self.source[self.i]) {
';' => _ = try skipToAndOver(self, '\n'),
'@' => {
self.i += 1;
const ident = try skipToAndOver(self, ' ');
skipSpace(self);
try requireEatBytes(self, "=");
skipSpace(self);
const decl = try parseInstruction(self, null, ident);
const ident_index = self.decls.items.len;
if (try self.global_name_map.put(ident, decl.inst)) |_| {
return self.fail("redefinition of identifier '{}'", .{ident});
}
try self.decls.append(self.allocator, decl);
},
' ', '\n' => self.i += 1,
0 => break,
else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}),
}
}
}
fn eatByte(self: *Parser, byte: u8) bool {
if (self.source[self.i] != byte) return false;
self.i += 1;
return true;
}
fn skipSpace(self: *Parser) void {
while (self.source[self.i] == ' ' or self.source[self.i] == '\n') {
self.i += 1;
}
}
fn requireEatBytes(self: *Parser, bytes: []const u8) !void {
const start = self.i;
for (bytes) |byte| {
if (self.source[self.i] != byte) {
self.i = start;
return self.fail("expected '{}'", .{bytes});
}
self.i += 1;
}
}
fn skipToAndOver(self: *Parser, byte: u8) ![]const u8 {
const start_i = self.i;
while (self.source[self.i] != 0) : (self.i += 1) {
if (self.source[self.i] == byte) {
const result = self.source[start_i..self.i];
self.i += 1;
return result;
}
}
return self.fail("unexpected EOF", .{});
}
/// ParseFailure is an internal error code; handled in `parse`.
const InnerError = error{ ParseFailure, OutOfMemory };
fn failByte(self: *Parser, byte: u8) InnerError {
if (byte == 0) {
return self.fail("unexpected EOF", .{});
} else {
return self.fail("unexpected byte: '{c}'", .{byte});
}
}
fn fail(self: *Parser, comptime format: []const u8, args: var) InnerError {
@setCold(true);
self.error_msg = ErrorMsg{
.byte_offset = self.i,
.msg = try std.fmt.allocPrint(&self.arena.allocator, format, args),
};
return error.ParseFailure;
}
fn parseInstruction(self: *Parser, body_ctx: ?*Body, name: []const u8) InnerError!*Decl {
const contents_start = self.i;
const fn_name = try skipToAndOver(self, '(');
inline for (@typeInfo(Inst.Tag).Enum.fields) |field| {
if (mem.eql(u8, field.name, fn_name)) {
const tag = @field(Inst.Tag, field.name);
return parseInstructionGeneric(self, field.name, Inst.TagToType(tag), body_ctx, name, contents_start);
}
}
return self.fail("unknown instruction '{}'", .{fn_name});
}
fn parseInstructionGeneric(
self: *Parser,
comptime fn_name: []const u8,
comptime InstType: type,
body_ctx: ?*Body,
inst_name: []const u8,
contents_start: usize,
) InnerError!*Decl {
const inst_specific = try self.arena.allocator.create(InstType);
inst_specific.base = .{
.src = self.i,
.tag = InstType.base_tag,
};
if (@hasField(InstType, "ty")) {
inst_specific.ty = opt_type orelse {
return self.fail("instruction '" ++ fn_name ++ "' requires type", .{});
};
}
const Positionals = @TypeOf(inst_specific.positionals);
inline for (@typeInfo(Positionals).Struct.fields) |arg_field| {
if (self.source[self.i] == ',') {
self.i += 1;
skipSpace(self);
} else if (self.source[self.i] == ')') {
return self.fail("expected positional parameter '{}'", .{arg_field.name});
}
@field(inst_specific.positionals, arg_field.name) = try parseParameterGeneric(
self,
arg_field.field_type,
body_ctx,
);
skipSpace(self);
}
const KW_Args = @TypeOf(inst_specific.kw_args);
inst_specific.kw_args = .{}; // assign defaults
skipSpace(self);
while (eatByte(self, ',')) {
skipSpace(self);
const name = try skipToAndOver(self, '=');
inline for (@typeInfo(KW_Args).Struct.fields) |arg_field| {
const field_name = arg_field.name;
if (mem.eql(u8, name, field_name)) {
const NonOptional = switch (@typeInfo(arg_field.field_type)) {
.Optional => |info| info.child,
else => arg_field.field_type,
};
@field(inst_specific.kw_args, field_name) = try parseParameterGeneric(self, NonOptional, body_ctx);
break;
}
} else {
return self.fail("unrecognized keyword parameter: '{}'", .{name});
}
skipSpace(self);
}
try requireEatBytes(self, ")");
const decl = try self.arena.allocator.create(Decl);
decl.* = .{
.name = inst_name,
.contents_hash = std.zig.hashSrc(self.source[contents_start..self.i]),
.inst = &inst_specific.base,
};
//std.debug.warn("parsed {} = '{}'\n", .{ inst_specific.base.name, inst_specific.base.contents });
return decl;
}
fn parseParameterGeneric(self: *Parser, comptime T: type, body_ctx: ?*Body) !T {
if (@typeInfo(T) == .Enum) {
const start = self.i;
while (true) : (self.i += 1) switch (self.source[self.i]) {
' ', '\n', ',', ')' => {
const enum_name = self.source[start..self.i];
return std.meta.stringToEnum(T, enum_name) orelse {
return self.fail("tag '{}' not a member of enum '{}'", .{ enum_name, @typeName(T) });
};
},
0 => return self.failByte(0),
else => continue,
};
}
switch (T) {
Module.Body => return parseBody(self, body_ctx),
bool => {
const bool_value = switch (self.source[self.i]) {
'0' => false,
'1' => true,
else => |byte| return self.fail("expected '0' or '1' for boolean value, found {c}", .{byte}),
};
self.i += 1;
return bool_value;
},
[]*Inst => {
try requireEatBytes(self, "[");
skipSpace(self);
if (eatByte(self, ']')) return &[0]*Inst{};
var instructions = std.ArrayList(*Inst).init(&self.arena.allocator);
while (true) {
skipSpace(self);
try instructions.append(try parseParameterInst(self, body_ctx));
skipSpace(self);
if (!eatByte(self, ',')) break;
}
try requireEatBytes(self, "]");
return instructions.toOwnedSlice();
},
*Inst => return parseParameterInst(self, body_ctx),
[]u8, []const u8 => return self.parseStringLiteral(),
BigIntConst => return self.parseIntegerLiteral(),
usize => {
const big_int = try self.parseIntegerLiteral();
return big_int.to(usize) catch |err| return self.fail("integer literal: {}", .{@errorName(err)});
},
TypedValue => return self.fail("'const' is a special instruction; not legal in ZIR text", .{}),
*IrModule.Decl => return self.fail("'declval_in_module' is a special instruction; not legal in ZIR text", .{}),
else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)),
}
return self.fail("TODO parse parameter {}", .{@typeName(T)});
}
fn parseParameterInst(self: *Parser, body_ctx: ?*Body) !*Inst {
const local_ref = switch (self.source[self.i]) {
'@' => false,
'%' => true,
else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}),
};
const map = if (local_ref)
if (body_ctx) |bc|
bc.name_map
else
return self.fail("referencing a % instruction in global scope", .{})
else
self.global_name_map;
self.i += 1;
const name_start = self.i;
while (true) : (self.i += 1) switch (self.source[self.i]) {
0, ' ', '\n', ',', ')', ']' => break,
else => continue,
};
const ident = self.source[name_start..self.i];
const kv = map.get(ident) orelse {
const bad_name = self.source[name_start - 1 .. self.i];
const src = name_start - 1;
if (local_ref) {
self.i = src;
return self.fail("unrecognized identifier: {}", .{bad_name});
} else {
const declval = try self.arena.allocator.create(Inst.DeclVal);
declval.* = .{
.base = .{
.src = src,
.tag = Inst.DeclVal.base_tag,
},
.positionals = .{ .name = ident },
.kw_args = .{},
};
return &declval.base;
}
};
return kv.value;
}
fn generateName(self: *Parser) ![]u8 {
const result = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${}", .{self.unnamed_index});
self.unnamed_index += 1;
return result;
}
};
pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {
var ctx: EmitZIR = .{
.allocator = allocator,
.decls = .{},
.arena = std.heap.ArenaAllocator.init(allocator),
.old_module = &old_module,
.next_auto_name = 0,
.names = std.StringHashMap(void).init(allocator),
.primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator),
};
defer ctx.decls.deinit(allocator);
defer ctx.names.deinit();
defer ctx.primitive_table.deinit();
errdefer ctx.arena.deinit();
try ctx.emit();
return Module{
.decls = ctx.decls.toOwnedSlice(allocator),
.arena = ctx.arena,
};
}
const EmitZIR = struct {
allocator: *Allocator,
arena: std.heap.ArenaAllocator,
old_module: *const IrModule,
decls: std.ArrayListUnmanaged(*Decl),
names: std.StringHashMap(void),
next_auto_name: usize,
primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl),
fn emit(self: *EmitZIR) !void {
// Put all the Decls in a list and sort them by name to avoid nondeterminism introduced
// by the hash table.
var src_decls = std.ArrayList(*IrModule.Decl).init(self.allocator);
defer src_decls.deinit();
try src_decls.ensureCapacity(self.old_module.decl_table.size);
try self.decls.ensureCapacity(self.allocator, self.old_module.decl_table.size);
try self.names.ensureCapacity(self.old_module.decl_table.size);
var decl_it = self.old_module.decl_table.iterator();
while (decl_it.next()) |kv| {
const decl = kv.value;
src_decls.appendAssumeCapacity(decl);
self.names.putAssumeCapacityNoClobber(mem.spanZ(decl.name), {});
}
std.sort.sort(*IrModule.Decl, src_decls.items, {}, (struct {
fn lessThan(context: void, a: *IrModule.Decl, b: *IrModule.Decl) bool {
return a.src_index < b.src_index;
}
}).lessThan);
// Emit all the decls.
for (src_decls.items) |ir_decl| {
switch (ir_decl.analysis) {
.unreferenced => continue,
.complete => {},
.in_progress => unreachable,
.outdated => unreachable,
.sema_failure,
.sema_failure_retryable,
.codegen_failure,
.dependency_failure,
.codegen_failure_retryable,
=> if (self.old_module.failed_decls.getValue(ir_decl)) |err_msg| {
const fail_inst = try self.arena.allocator.create(Inst.CompileError);
fail_inst.* = .{
.base = .{
.src = ir_decl.src(),
.tag = Inst.CompileError.base_tag,
},
.positionals = .{
.msg = try self.arena.allocator.dupe(u8, err_msg.msg),
},
.kw_args = .{},
};
const decl = try self.arena.allocator.create(Decl);
decl.* = .{
.name = mem.spanZ(ir_decl.name),
.contents_hash = undefined,
.inst = &fail_inst.base,
};
try self.decls.append(self.allocator, decl);
continue;
},
}
if (self.old_module.export_owners.getValue(ir_decl)) |exports| {
for (exports) |module_export| {
const symbol_name = try self.emitStringLiteral(module_export.src, module_export.options.name);
const export_inst = try self.arena.allocator.create(Inst.Export);
export_inst.* = .{
.base = .{
.src = module_export.src,
.tag = Inst.Export.base_tag,
},
.positionals = .{
.symbol_name = symbol_name.inst,
.decl_name = mem.spanZ(module_export.exported_decl.name),
},
.kw_args = .{},
};
_ = try self.emitUnnamedDecl(&export_inst.base);
}
} else {
const new_decl = try self.emitTypedValue(ir_decl.src(), ir_decl.typed_value.most_recent.typed_value);
new_decl.name = try self.arena.allocator.dupe(u8, mem.spanZ(ir_decl.name));
}
}
}
const ZirBody = struct {
inst_table: *std.AutoHashMap(*ir.Inst, *Inst),
instructions: *std.ArrayList(*Inst),
};
fn resolveInst(self: *EmitZIR, new_body: ZirBody, inst: *ir.Inst) !*Inst {
if (inst.cast(ir.Inst.Constant)) |const_inst| {
const new_inst = if (const_inst.val.cast(Value.Payload.Function)) |func_pl| blk: {
const owner_decl = func_pl.func.owner_decl;
break :blk try self.emitDeclVal(inst.src, mem.spanZ(owner_decl.name));
} else if (const_inst.val.cast(Value.Payload.DeclRef)) |declref| blk: {
const decl_ref = try self.emitDeclRef(inst.src, declref.decl);
try new_body.instructions.append(decl_ref);
break :blk decl_ref;
} else blk: {
break :blk (try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val })).inst;
};
try new_body.inst_table.putNoClobber(inst, new_inst);
return new_inst;
} else {
return new_body.inst_table.getValue(inst).?;
}
}
fn emitDeclVal(self: *EmitZIR, src: usize, decl_name: []const u8) !*Inst {
const declval = try self.arena.allocator.create(Inst.DeclVal);
declval.* = .{
.base = .{
.src = src,
.tag = Inst.DeclVal.base_tag,
},
.positionals = .{ .name = try self.arena.allocator.dupe(u8, decl_name) },
.kw_args = .{},
};
return &declval.base;
}
fn emitComptimeIntVal(self: *EmitZIR, src: usize, val: Value) !*Decl {
const big_int_space = try self.arena.allocator.create(Value.BigIntSpace);
const int_inst = try self.arena.allocator.create(Inst.Int);
int_inst.* = .{
.base = .{
.src = src,
.tag = Inst.Int.base_tag,
},
.positionals = .{
.int = val.toBigInt(big_int_space),
},
.kw_args = .{},
};
return self.emitUnnamedDecl(&int_inst.base);
}
fn emitDeclRef(self: *EmitZIR, src: usize, module_decl: *IrModule.Decl) !*Inst {
const declref_inst = try self.arena.allocator.create(Inst.DeclRef);
declref_inst.* = .{
.base = .{
.src = src,
.tag = Inst.DeclRef.base_tag,
},
.positionals = .{
.name = mem.spanZ(module_decl.name),
},
.kw_args = .{},
};
return &declref_inst.base;
}
fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Decl {
const allocator = &self.arena.allocator;
if (typed_value.val.cast(Value.Payload.DeclRef)) |decl_ref| {
const decl = decl_ref.decl;
return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl));
}
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 array_elem_type = ptr_elem_type.elemType();
//if (array_elem_type.eql(Type.initTag(.u8)) and
// ptr_elem_type.hasSentinel(Value.initTag(.zero)))
//{
//}
const bytes = typed_value.val.toAllocatedBytes(allocator) catch |err| switch (err) {
error.AnalysisFail => unreachable,
else => |e| return e,
};
return self.emitStringLiteral(src, bytes);
},
else => |t| std.debug.panic("TODO implement emitTypedValue for pointer to {}", .{@tagName(t)}),
}
},
.ComptimeInt => return self.emitComptimeIntVal(src, typed_value.val),
.Int => {
const as_inst = try self.arena.allocator.create(Inst.As);
as_inst.* = .{
.base = .{
.src = src,
.tag = Inst.As.base_tag,
},
.positionals = .{
.dest_type = (try self.emitType(src, typed_value.ty)).inst,
.value = (try self.emitComptimeIntVal(src, typed_value.val)).inst,
},
.kw_args = .{},
};
return self.emitUnnamedDecl(&as_inst.base);
},
.Type => {
const ty = typed_value.val.toType();
return self.emitType(src, ty);
},
.Fn => {
const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;
var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator);
defer inst_table.deinit();
var instructions = std.ArrayList(*Inst).init(self.allocator);
defer instructions.deinit();
switch (module_fn.analysis) {
.queued => unreachable,
.in_progress => unreachable,
.success => |body| {
try self.emitBody(body, &inst_table, &instructions);
},
.sema_failure => {
const err_msg = self.old_module.failed_decls.getValue(module_fn.owner_decl).?;
const fail_inst = try self.arena.allocator.create(Inst.CompileError);
fail_inst.* = .{
.base = .{
.src = src,
.tag = Inst.CompileError.base_tag,
},
.positionals = .{
.msg = try self.arena.allocator.dupe(u8, err_msg.msg),
},
.kw_args = .{},
};
try instructions.append(&fail_inst.base);
},
.dependency_failure => {
const fail_inst = try self.arena.allocator.create(Inst.CompileError);
fail_inst.* = .{
.base = .{
.src = src,
.tag = Inst.CompileError.base_tag,
},
.positionals = .{
.msg = try self.arena.allocator.dupe(u8, "depends on another failed Decl"),
},
.kw_args = .{},
};
try instructions.append(&fail_inst.base);
},
}
const fn_type = try self.emitType(src, typed_value.ty);
const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len);
mem.copy(*Inst, arena_instrs, instructions.items);
const fn_inst = try self.arena.allocator.create(Inst.Fn);
fn_inst.* = .{
.base = .{
.src = src,
.tag = Inst.Fn.base_tag,
},
.positionals = .{
.fn_type = fn_type.inst,
.body = .{ .instructions = arena_instrs },
},
.kw_args = .{},
};
return self.emitUnnamedDecl(&fn_inst.base);
},
.Array => {
// TODO more checks to make sure this can be emitted as a string literal
//const array_elem_type = ptr_elem_type.elemType();
//if (array_elem_type.eql(Type.initTag(.u8)) and
// ptr_elem_type.hasSentinel(Value.initTag(.zero)))
//{
//}
const bytes = typed_value.val.toAllocatedBytes(allocator) catch |err| switch (err) {
error.AnalysisFail => unreachable,
else => |e| return e,
};
const str_inst = try self.arena.allocator.create(Inst.Str);
str_inst.* = .{
.base = .{
.src = src,
.tag = Inst.Str.base_tag,
},
.positionals = .{
.bytes = bytes,
},
.kw_args = .{},
};
return self.emitUnnamedDecl(&str_inst.base);
},
.Void => return self.emitPrimitive(src, .void_value),
else => |t| std.debug.panic("TODO implement emitTypedValue for {}", .{@tagName(t)}),
}
}
fn emitTrivial(self: *EmitZIR, src: usize, comptime T: type) Allocator.Error!*Inst {
const new_inst = try self.arena.allocator.create(T);
new_inst.* = .{
.base = .{
.src = src,
.tag = T.base_tag,
},
.positionals = .{},
.kw_args = .{},
};
return &new_inst.base;
}
fn emitBody(
self: *EmitZIR,
body: ir.Body,
inst_table: *std.AutoHashMap(*ir.Inst, *Inst),
instructions: *std.ArrayList(*Inst),
) Allocator.Error!void {
const new_body = ZirBody{
.inst_table = inst_table,
.instructions = instructions,
};
for (body.instructions) |inst| {
const new_inst = switch (inst.tag) {
.add => blk: {
const old_inst = inst.cast(ir.Inst.Add).?;
const new_inst = try self.arena.allocator.create(Inst.Add);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.Add.base_tag,
},
.positionals = .{
.lhs = try self.resolveInst(new_body, old_inst.args.lhs),
.rhs = try self.resolveInst(new_body, old_inst.args.rhs),
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
.arg => blk: {
const old_inst = inst.cast(ir.Inst.Arg).?;
const new_inst = try self.arena.allocator.create(Inst.Arg);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.Arg.base_tag,
},
.positionals = .{ .index = old_inst.args.index },
.kw_args = .{},
};
break :blk &new_inst.base;
},
.block => blk: {
const old_inst = inst.cast(ir.Inst.Block).?;
const new_inst = try self.arena.allocator.create(Inst.Block);
var block_body = std.ArrayList(*Inst).init(self.allocator);
defer block_body.deinit();
try self.emitBody(old_inst.args.body, inst_table, &block_body);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.Block.base_tag,
},
.positionals = .{
.label = try self.autoName(),
.body = .{ .instructions = block_body.toOwnedSlice() },
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
.breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint),
.call => blk: {
const old_inst = inst.cast(ir.Inst.Call).?;
const new_inst = try self.arena.allocator.create(Inst.Call);
const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len);
for (args) |*elem, i| {
elem.* = try self.resolveInst(new_body, old_inst.args.args[i]);
}
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.Call.base_tag,
},
.positionals = .{
.func = try self.resolveInst(new_body, old_inst.args.func),
.args = args,
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
.unreach => try self.emitTrivial(inst.src, Inst.Unreachable),
.ret => blk: {
const old_inst = inst.cast(ir.Inst.Ret).?;
const new_inst = try self.arena.allocator.create(Inst.Return);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.Return.base_tag,
},
.positionals = .{
.operand = try self.resolveInst(new_body, old_inst.args.operand),
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
.retvoid => try self.emitTrivial(inst.src, Inst.ReturnVoid),
.constant => unreachable, // excluded from function bodies
.assembly => blk: {
const old_inst = inst.cast(ir.Inst.Assembly).?;
const new_inst = try self.arena.allocator.create(Inst.Asm);
const inputs = try self.arena.allocator.alloc(*Inst, old_inst.args.inputs.len);
for (inputs) |*elem, i| {
elem.* = (try self.emitStringLiteral(inst.src, old_inst.args.inputs[i])).inst;
}
const clobbers = try self.arena.allocator.alloc(*Inst, old_inst.args.clobbers.len);
for (clobbers) |*elem, i| {
elem.* = (try self.emitStringLiteral(inst.src, old_inst.args.clobbers[i])).inst;
}
const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len);
for (args) |*elem, i| {
elem.* = try self.resolveInst(new_body, old_inst.args.args[i]);
}
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.Asm.base_tag,
},
.positionals = .{
.asm_source = (try self.emitStringLiteral(inst.src, old_inst.args.asm_source)).inst,
.return_type = (try self.emitType(inst.src, inst.ty)).inst,
},
.kw_args = .{
.@"volatile" = old_inst.args.is_volatile,
.output = if (old_inst.args.output) |o|
(try self.emitStringLiteral(inst.src, o)).inst
else
null,
.inputs = inputs,
.clobbers = clobbers,
.args = args,
},
};
break :blk &new_inst.base;
},
.ptrtoint => blk: {
const old_inst = inst.cast(ir.Inst.PtrToInt).?;
const new_inst = try self.arena.allocator.create(Inst.PtrToInt);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.PtrToInt.base_tag,
},
.positionals = .{
.ptr = try self.resolveInst(new_body, old_inst.args.ptr),
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
.bitcast => blk: {
const old_inst = inst.cast(ir.Inst.BitCast).?;
const new_inst = try self.arena.allocator.create(Inst.BitCast);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.BitCast.base_tag,
},
.positionals = .{
.dest_type = (try self.emitType(inst.src, inst.ty)).inst,
.operand = try self.resolveInst(new_body, old_inst.args.operand),
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
.cmp => blk: {
const old_inst = inst.cast(ir.Inst.Cmp).?;
const new_inst = try self.arena.allocator.create(Inst.Cmp);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.Cmp.base_tag,
},
.positionals = .{
.lhs = try self.resolveInst(new_body, old_inst.args.lhs),
.rhs = try self.resolveInst(new_body, old_inst.args.rhs),
.op = old_inst.args.op,
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
.condbr => blk: {
const old_inst = inst.cast(ir.Inst.CondBr).?;
var true_body = std.ArrayList(*Inst).init(self.allocator);
var false_body = std.ArrayList(*Inst).init(self.allocator);
defer true_body.deinit();
defer false_body.deinit();
try self.emitBody(old_inst.args.true_body, inst_table, &true_body);
try self.emitBody(old_inst.args.false_body, inst_table, &false_body);
const new_inst = try self.arena.allocator.create(Inst.CondBr);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.CondBr.base_tag,
},
.positionals = .{
.condition = try self.resolveInst(new_body, old_inst.args.condition),
.true_body = .{ .instructions = true_body.toOwnedSlice() },
.false_body = .{ .instructions = false_body.toOwnedSlice() },
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
.isnull => blk: {
const old_inst = inst.cast(ir.Inst.IsNull).?;
const new_inst = try self.arena.allocator.create(Inst.IsNull);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.IsNull.base_tag,
},
.positionals = .{
.operand = try self.resolveInst(new_body, old_inst.args.operand),
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
.isnonnull => blk: {
const old_inst = inst.cast(ir.Inst.IsNonNull).?;
const new_inst = try self.arena.allocator.create(Inst.IsNonNull);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.IsNonNull.base_tag,
},
.positionals = .{
.operand = try self.resolveInst(new_body, old_inst.args.operand),
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
};
try instructions.append(new_inst);
try inst_table.putNoClobber(inst, new_inst);
}
}
fn emitType(self: *EmitZIR, src: usize, ty: Type) Allocator.Error!*Decl {
switch (ty.tag()) {
.i8 => return self.emitPrimitive(src, .i8),
.u8 => return self.emitPrimitive(src, .u8),
.i16 => return self.emitPrimitive(src, .i16),
.u16 => return self.emitPrimitive(src, .u16),
.i32 => return self.emitPrimitive(src, .i32),
.u32 => return self.emitPrimitive(src, .u32),
.i64 => return self.emitPrimitive(src, .i64),
.u64 => return self.emitPrimitive(src, .u64),
.isize => return self.emitPrimitive(src, .isize),
.usize => return self.emitPrimitive(src, .usize),
.c_short => return self.emitPrimitive(src, .c_short),
.c_ushort => return self.emitPrimitive(src, .c_ushort),
.c_int => return self.emitPrimitive(src, .c_int),
.c_uint => return self.emitPrimitive(src, .c_uint),
.c_long => return self.emitPrimitive(src, .c_long),
.c_ulong => return self.emitPrimitive(src, .c_ulong),
.c_longlong => return self.emitPrimitive(src, .c_longlong),
.c_ulonglong => return self.emitPrimitive(src, .c_ulonglong),
.c_longdouble => return self.emitPrimitive(src, .c_longdouble),
.c_void => return self.emitPrimitive(src, .c_void),
.f16 => return self.emitPrimitive(src, .f16),
.f32 => return self.emitPrimitive(src, .f32),
.f64 => return self.emitPrimitive(src, .f64),
.f128 => return self.emitPrimitive(src, .f128),
.anyerror => return self.emitPrimitive(src, .anyerror),
else => switch (ty.zigTypeTag()) {
.Bool => return self.emitPrimitive(src, .bool),
.Void => return self.emitPrimitive(src, .void),
.NoReturn => return self.emitPrimitive(src, .noreturn),
.Type => return self.emitPrimitive(src, .type),
.ComptimeInt => return self.emitPrimitive(src, .comptime_int),
.ComptimeFloat => return self.emitPrimitive(src, .comptime_float),
.Fn => {
const param_types = try self.allocator.alloc(Type, ty.fnParamLen());
defer self.allocator.free(param_types);
ty.fnParamTypes(param_types);
const emitted_params = try self.arena.allocator.alloc(*Inst, param_types.len);
for (param_types) |param_type, i| {
emitted_params[i] = (try self.emitType(src, param_type)).inst;
}
const fntype_inst = try self.arena.allocator.create(Inst.FnType);
fntype_inst.* = .{
.base = .{
.src = src,
.tag = Inst.FnType.base_tag,
},
.positionals = .{
.param_types = emitted_params,
.return_type = (try self.emitType(src, ty.fnReturnType())).inst,
},
.kw_args = .{
.cc = ty.fnCallingConvention(),
},
};
return self.emitUnnamedDecl(&fntype_inst.base);
},
else => std.debug.panic("TODO implement emitType for {}", .{ty}),
},
}
}
fn autoName(self: *EmitZIR) ![]u8 {
while (true) {
const proposed_name = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${}", .{self.next_auto_name});
self.next_auto_name += 1;
const gop = try self.names.getOrPut(proposed_name);
if (!gop.found_existing) {
gop.kv.value = {};
return proposed_name;
}
}
}
fn emitPrimitive(self: *EmitZIR, src: usize, tag: Inst.Primitive.Builtin) !*Decl {
const gop = try self.primitive_table.getOrPut(tag);
if (!gop.found_existing) {
const primitive_inst = try self.arena.allocator.create(Inst.Primitive);
primitive_inst.* = .{
.base = .{
.src = src,
.tag = Inst.Primitive.base_tag,
},
.positionals = .{
.tag = tag,
},
.kw_args = .{},
};
gop.kv.value = try self.emitUnnamedDecl(&primitive_inst.base);
}
return gop.kv.value;
}
fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Decl {
const str_inst = try self.arena.allocator.create(Inst.Str);
str_inst.* = .{
.base = .{
.src = src,
.tag = Inst.Str.base_tag,
},
.positionals = .{
.bytes = str,
},
.kw_args = .{},
};
return self.emitUnnamedDecl(&str_inst.base);
}
fn emitUnnamedDecl(self: *EmitZIR, inst: *Inst) !*Decl {
const decl = try self.arena.allocator.create(Decl);
decl.* = .{
.name = try self.autoName(),
.contents_hash = undefined,
.inst = inst,
};
try self.decls.append(self.allocator, decl);
return decl;
}
};