zig/src-self-hosted/ir.zig

1392 lines
49 KiB
Zig
Raw Normal View History

const std = @import("std");
const builtin = @import("builtin");
const Compilation = @import("compilation.zig").Compilation;
const Scope = @import("scope.zig").Scope;
const ast = std.zig.ast;
const Allocator = std.mem.Allocator;
const Value = @import("value.zig").Value;
const Type = Value.Type;
const assert = std.debug.assert;
const Token = std.zig.Token;
const ParsedFile = @import("parsed_file.zig").ParsedFile;
2018-07-13 18:56:38 -07:00
const Span = @import("errmsg.zig").Span;
const llvm = @import("llvm.zig");
const ObjectFile = @import("codegen.zig").ObjectFile;
pub const LVal = enum {
None,
Ptr,
};
pub const IrVal = union(enum) {
Unknown,
2018-07-13 18:56:38 -07:00
KnownType: *Type,
KnownValue: *Value,
const Init = enum {
Unknown,
NoReturn,
Void,
};
pub fn dump(self: IrVal) void {
switch (self) {
2018-07-13 18:56:38 -07:00
IrVal.Unknown => typeof.dump(),
IrVal.KnownType => |typeof| {
std.debug.warn("KnownType(");
typeof.dump();
std.debug.warn(")");
},
IrVal.KnownValue => |value| {
std.debug.warn("KnownValue(");
value.dump();
std.debug.warn(")");
},
}
}
};
pub const Inst = struct {
id: Id,
scope: *Scope,
debug_id: usize,
val: IrVal,
2018-07-13 18:56:38 -07:00
ref_count: usize,
span: Span,
owner_bb: *BasicBlock,
/// true if this instruction was generated by zig and not from user code
is_generated: bool,
2018-07-13 18:56:38 -07:00
/// the instruction that is derived from this one in analysis
child: ?*Inst,
2018-07-13 18:56:38 -07:00
/// the instruction that this one derives from in analysis
parent: ?*Inst,
2018-07-13 18:56:38 -07:00
/// populated durign codegen
llvm_value: ?llvm.ValueRef,
pub fn cast(base: *Inst, comptime T: type) ?*T {
if (base.id == comptime typeToId(T)) {
return @fieldParentPtr(T, "base", base);
}
return null;
}
pub fn typeToId(comptime T: type) Id {
comptime var i = 0;
inline while (i < @memberCount(Id)) : (i += 1) {
if (T == @field(Inst, @memberName(Id, i))) {
return @field(Id, @memberName(Id, i));
}
}
unreachable;
}
pub fn dump(base: *const Inst) void {
comptime var i = 0;
inline while (i < @memberCount(Id)) : (i += 1) {
if (base.id == @field(Id, @memberName(Id, i))) {
const T = @field(Inst, @memberName(Id, i));
std.debug.warn("#{} = {}(", base.debug_id, @tagName(base.id));
@fieldParentPtr(T, "base", base).dump();
std.debug.warn(")");
return;
}
}
unreachable;
}
pub fn hasSideEffects(base: *const Inst) bool {
2018-07-13 18:56:38 -07:00
comptime var i = 0;
inline while (i < @memberCount(Id)) : (i += 1) {
if (base.id == @field(Id, @memberName(Id, i))) {
const T = @field(Inst, @memberName(Id, i));
2018-07-13 18:56:38 -07:00
return @fieldParentPtr(T, "base", base).hasSideEffects();
}
}
unreachable;
}
pub fn analyze(base: *Inst, ira: *Analyze) Analyze.Error!*Inst {
2018-07-13 18:56:38 -07:00
comptime var i = 0;
inline while (i < @memberCount(Id)) : (i += 1) {
if (base.id == @field(Id, @memberName(Id, i))) {
const T = @field(Inst, @memberName(Id, i));
return @fieldParentPtr(T, "base", base).analyze(ira);
2018-07-13 18:56:38 -07:00
}
}
unreachable;
}
pub fn render(base: *Inst, ofile: *ObjectFile, fn_val: *Value.Fn) (error{OutOfMemory}!?llvm.ValueRef) {
switch (base.id) {
Id.Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val),
Id.Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val),
Id.Ref => @panic("TODO"),
Id.DeclVar => @panic("TODO"),
Id.CheckVoidStmt => @panic("TODO"),
Id.Phi => @panic("TODO"),
Id.Br => @panic("TODO"),
Id.AddImplicitReturnType => @panic("TODO"),
}
}
fn ref(base: *Inst, builder: *Builder) void {
base.ref_count += 1;
if (base.owner_bb != builder.current_basic_block and !base.isCompTime()) {
base.owner_bb.ref();
}
}
fn getAsParam(param: *Inst) !*Inst {
2018-07-13 18:56:38 -07:00
const child = param.child orelse return error.SemanticAnalysisFailed;
switch (child.val) {
IrVal.Unknown => return error.SemanticAnalysisFailed,
else => return child,
}
}
/// asserts that the type is known
fn getKnownType(self: *Inst) *Type {
2018-07-13 18:56:38 -07:00
switch (self.val) {
IrVal.KnownType => |typeof| return typeof,
IrVal.KnownValue => |value| return value.typeof,
IrVal.Unknown => unreachable,
}
}
pub fn setGenerated(base: *Inst) void {
base.is_generated = true;
}
pub fn isNoReturn(base: *const Inst) bool {
switch (base.val) {
IrVal.Unknown => return false,
2018-07-13 18:56:38 -07:00
IrVal.KnownValue => |x| return x.typeof.id == Type.Id.NoReturn,
IrVal.KnownType => |typeof| return typeof.id == Type.Id.NoReturn,
}
}
pub fn isCompTime(base: *const Inst) bool {
return base.val == IrVal.KnownValue;
}
pub fn linkToParent(self: *Inst, parent: *Inst) void {
2018-07-13 18:56:38 -07:00
assert(self.parent == null);
assert(parent.child == null);
self.parent = parent;
parent.child = self;
}
pub const Id = enum {
Return,
Const,
Ref,
DeclVar,
CheckVoidStmt,
Phi,
Br,
2018-07-13 18:56:38 -07:00
AddImplicitReturnType,
};
pub const Const = struct {
base: Inst,
2018-07-13 18:56:38 -07:00
params: Params,
2018-07-13 18:56:38 -07:00
const Params = struct {};
// Use Builder.buildConst* methods, or, after building a Const instruction,
// manually set the ir_val field.
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(self: *const Const) void {
self.base.val.KnownValue.dump();
}
2018-07-13 18:56:38 -07:00
pub fn hasSideEffects(self: *const Const) bool {
return false;
}
pub fn analyze(self: *const Const, ira: *Analyze) !*Inst {
2018-07-13 18:56:38 -07:00
const new_inst = try ira.irb.build(Const, self.base.scope, self.base.span, Params{});
new_inst.val = IrVal{ .KnownValue = self.base.val.KnownValue.getRef() };
return new_inst;
}
pub fn render(self: *Const, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef {
return self.base.val.KnownValue.getLlvmConst(ofile);
}
};
pub const Return = struct {
base: Inst,
2018-07-13 18:56:38 -07:00
params: Params,
const Params = struct {
return_value: *Inst,
2018-07-13 18:56:38 -07:00
};
const ir_val_init = IrVal.Init.NoReturn;
pub fn dump(self: *const Return) void {
std.debug.warn("#{}", self.params.return_value.debug_id);
}
2018-07-13 18:56:38 -07:00
pub fn hasSideEffects(self: *const Return) bool {
return true;
}
pub fn analyze(self: *const Return, ira: *Analyze) !*Inst {
2018-07-13 18:56:38 -07:00
const value = try self.params.return_value.getAsParam();
const casted_value = try ira.implicitCast(value, ira.explicit_return_type);
// TODO detect returning local variable address
return ira.irb.build(Return, self.base.scope, self.base.span, Params{ .return_value = casted_value });
}
pub fn render(self: *Return, ofile: *ObjectFile, fn_val: *Value.Fn) ?llvm.ValueRef {
const value = self.params.return_value.llvm_value;
const return_type = self.params.return_value.getKnownType();
if (return_type.handleIsPtr()) {
@panic("TODO");
} else {
_ = llvm.BuildRet(ofile.builder, value);
}
return null;
}
};
pub const Ref = struct {
base: Inst,
2018-07-13 18:56:38 -07:00
params: Params,
2018-07-13 18:56:38 -07:00
const Params = struct {
target: *Inst,
2018-07-13 18:56:38 -07:00
mut: Type.Pointer.Mut,
volatility: Type.Pointer.Vol,
};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const Ref) void {}
pub fn hasSideEffects(inst: *const Ref) bool {
return false;
}
pub fn analyze(self: *const Ref, ira: *Analyze) !*Inst {
2018-07-13 18:56:38 -07:00
const target = try self.params.target.getAsParam();
if (ira.getCompTimeValOrNullUndefOk(target)) |val| {
return ira.getCompTimeRef(
val,
Value.Ptr.Mut.CompTimeConst,
self.params.mut,
self.params.volatility,
val.typeof.getAbiAlignment(ira.irb.comp),
2018-07-13 18:56:38 -07:00
);
}
const new_inst = try ira.irb.build(Ref, self.base.scope, self.base.span, Params{
.target = target,
2018-07-13 18:56:38 -07:00
.mut = self.params.mut,
.volatility = self.params.volatility,
});
2018-07-13 18:56:38 -07:00
const elem_type = target.getKnownType();
const ptr_type = Type.Pointer.get(
ira.irb.comp,
2018-07-13 18:56:38 -07:00
elem_type,
self.params.mut,
self.params.volatility,
Type.Pointer.Size.One,
elem_type.getAbiAlignment(ira.irb.comp),
2018-07-13 18:56:38 -07:00
);
// TODO: potentially set the hint that this is a stack pointer. But it might not be - this
// could be a ref of a global, for example
new_inst.val = IrVal{ .KnownType = &ptr_type.base };
// TODO potentially add an alloca entry here
return new_inst;
}
};
pub const DeclVar = struct {
base: Inst,
2018-07-13 18:56:38 -07:00
params: Params,
const Params = struct {
variable: *Variable,
};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const DeclVar) void {}
2018-07-13 18:56:38 -07:00
pub fn hasSideEffects(inst: *const DeclVar) bool {
return true;
}
pub fn analyze(self: *const DeclVar, ira: *Analyze) !*Inst {
2018-07-13 18:56:38 -07:00
return error.Unimplemented; // TODO
}
};
pub const CheckVoidStmt = struct {
base: Inst,
2018-07-13 18:56:38 -07:00
params: Params,
2018-07-13 18:56:38 -07:00
const Params = struct {
target: *Inst,
2018-07-13 18:56:38 -07:00
};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const CheckVoidStmt) void {}
2018-07-13 18:56:38 -07:00
pub fn hasSideEffects(inst: *const CheckVoidStmt) bool {
return true;
}
pub fn analyze(self: *const CheckVoidStmt, ira: *Analyze) !*Inst {
2018-07-13 18:56:38 -07:00
return error.Unimplemented; // TODO
}
};
pub const Phi = struct {
base: Inst,
2018-07-13 18:56:38 -07:00
params: Params,
2018-07-13 18:56:38 -07:00
const Params = struct {
incoming_blocks: []*BasicBlock,
incoming_values: []*Inst,
2018-07-13 18:56:38 -07:00
};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const Phi) void {}
2018-07-13 18:56:38 -07:00
pub fn hasSideEffects(inst: *const Phi) bool {
return false;
}
pub fn analyze(self: *const Phi, ira: *Analyze) !*Inst {
2018-07-13 18:56:38 -07:00
return error.Unimplemented; // TODO
}
};
pub const Br = struct {
base: Inst,
2018-07-13 18:56:38 -07:00
params: Params,
2018-07-13 18:56:38 -07:00
const Params = struct {
dest_block: *BasicBlock,
is_comptime: *Inst,
2018-07-13 18:56:38 -07:00
};
const ir_val_init = IrVal.Init.NoReturn;
pub fn dump(inst: *const Br) void {}
2018-07-13 18:56:38 -07:00
pub fn hasSideEffects(inst: *const Br) bool {
return true;
}
pub fn analyze(self: *const Br, ira: *Analyze) !*Inst {
return error.Unimplemented; // TODO
}
};
pub const CondBr = struct {
base: Inst,
params: Params,
const Params = struct {
condition: *Inst,
then_block: *BasicBlock,
else_block: *BasicBlock,
is_comptime: *Inst,
};
const ir_val_init = IrVal.Init.NoReturn;
pub fn dump(inst: *const CondBr) void {}
pub fn hasSideEffects(inst: *const CondBr) bool {
return true;
}
pub fn analyze(self: *const CondBr, ira: *Analyze) !*Inst {
2018-07-13 18:56:38 -07:00
return error.Unimplemented; // TODO
}
};
pub const AddImplicitReturnType = struct {
base: Inst,
2018-07-13 18:56:38 -07:00
params: Params,
pub const Params = struct {
target: *Inst,
2018-07-13 18:56:38 -07:00
};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const AddImplicitReturnType) void {
std.debug.warn("#{}", inst.params.target.debug_id);
}
pub fn hasSideEffects(inst: *const AddImplicitReturnType) bool {
return true;
}
pub fn analyze(self: *const AddImplicitReturnType, ira: *Analyze) !*Inst {
2018-07-13 18:56:38 -07:00
const target = try self.params.target.getAsParam();
try ira.src_implicit_return_type_list.append(target);
return ira.irb.buildConstVoid(self.base.scope, self.base.span, true);
2018-07-13 18:56:38 -07:00
}
};
pub const TestErr = struct {
base: Inst,
params: Params,
pub const Params = struct {
target: *Inst,
};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const TestErr) void {
std.debug.warn("#{}", inst.params.target.debug_id);
}
pub fn hasSideEffects(inst: *const TestErr) bool {
return false;
}
pub fn analyze(self: *const TestErr, ira: *Analyze) !*Inst {
const target = try self.params.target.getAsParam();
const target_type = target.getKnownType();
switch (target_type.id) {
Type.Id.ErrorUnion => {
return error.Unimplemented;
// if (instr_is_comptime(value)) {
// ConstExprValue *err_union_val = ir_resolve_const(ira, value, UndefBad);
// if (!err_union_val)
// return ira->codegen->builtin_types.entry_invalid;
// if (err_union_val->special != ConstValSpecialRuntime) {
// ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
// out_val->data.x_bool = (err_union_val->data.x_err_union.err != nullptr);
// return ira->codegen->builtin_types.entry_bool;
// }
// }
// TypeTableEntry *err_set_type = type_entry->data.error_union.err_set_type;
// if (!resolve_inferred_error_set(ira->codegen, err_set_type, instruction->base.source_node)) {
// return ira->codegen->builtin_types.entry_invalid;
// }
// if (!type_is_global_error_set(err_set_type) &&
// err_set_type->data.error_set.err_count == 0)
// {
// assert(err_set_type->data.error_set.infer_fn == nullptr);
// ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
// out_val->data.x_bool = false;
// return ira->codegen->builtin_types.entry_bool;
// }
// ir_build_test_err_from(&ira->new_irb, &instruction->base, value);
// return ira->codegen->builtin_types.entry_bool;
},
Type.Id.ErrorSet => {
return ira.irb.buildConstBool(self.base.scope, self.base.span, true);
},
else => {
return ira.irb.buildConstBool(self.base.scope, self.base.span, false);
},
}
}
};
pub const TestCompTime = struct {
base: Inst,
params: Params,
pub const Params = struct {
target: *Inst,
};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const TestCompTime) void {
std.debug.warn("#{}", inst.params.target.debug_id);
}
pub fn hasSideEffects(inst: *const TestCompTime) bool {
return false;
}
pub fn analyze(self: *const TestCompTime, ira: *Analyze) !*Inst {
const target = try self.params.target.getAsParam();
return ira.irb.buildConstBool(self.base.scope, self.base.span, target.isCompTime());
}
};
pub const SaveErrRetAddr = struct {
base: Inst,
params: Params,
const Params = struct {};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const SaveErrRetAddr) void {}
pub fn hasSideEffects(inst: *const SaveErrRetAddr) bool {
return true;
}
pub fn analyze(self: *const SaveErrRetAddr, ira: *Analyze) !*Inst {
return ira.irb.build(Inst.SaveErrRetAddr, self.base.scope, self.base.span, Params{});
}
};
};
pub const Variable = struct {
child_scope: *Scope,
};
pub const BasicBlock = struct {
ref_count: usize,
name_hint: [*]const u8, // must be a C string literal
debug_id: usize,
scope: *Scope,
instruction_list: std.ArrayList(*Inst),
ref_instruction: ?*Inst,
2018-07-13 18:56:38 -07:00
/// for codegen
llvm_block: llvm.BasicBlockRef,
llvm_exit_block: llvm.BasicBlockRef,
2018-07-13 18:56:38 -07:00
/// the basic block that is derived from this one in analysis
child: ?*BasicBlock,
/// the basic block that this one derives from in analysis
parent: ?*BasicBlock,
pub fn ref(self: *BasicBlock) void {
self.ref_count += 1;
}
2018-07-13 18:56:38 -07:00
pub fn linkToParent(self: *BasicBlock, parent: *BasicBlock) void {
assert(self.parent == null);
assert(parent.child == null);
self.parent = parent;
parent.child = self;
}
};
/// Stuff that survives longer than Builder
pub const Code = struct {
basic_block_list: std.ArrayList(*BasicBlock),
arena: std.heap.ArenaAllocator,
2018-07-13 18:56:38 -07:00
return_type: ?*Type,
/// allocator is comp.gpa()
pub fn destroy(self: *Code, allocator: *Allocator) void {
self.arena.deinit();
allocator.destroy(self);
}
pub fn dump(self: *Code) void {
var bb_i: usize = 0;
for (self.basic_block_list.toSliceConst()) |bb| {
std.debug.warn("{s}_{}:\n", bb.name_hint, bb.debug_id);
for (bb.instruction_list.toSliceConst()) |instr| {
std.debug.warn(" ");
instr.dump();
std.debug.warn("\n");
}
}
}
};
pub const Builder = struct {
comp: *Compilation,
code: *Code,
current_basic_block: *BasicBlock,
next_debug_id: usize,
parsed_file: *ParsedFile,
is_comptime: bool,
is_async: bool,
begin_scope: ?*Scope,
2018-07-13 18:56:38 -07:00
pub const Error = Analyze.Error;
pub fn init(comp: *Compilation, parsed_file: *ParsedFile, begin_scope: ?*Scope) !Builder {
const code = try comp.gpa().create(Code{
.basic_block_list = undefined,
.arena = std.heap.ArenaAllocator.init(comp.gpa()),
2018-07-13 18:56:38 -07:00
.return_type = null,
});
code.basic_block_list = std.ArrayList(*BasicBlock).init(&code.arena.allocator);
errdefer code.destroy(comp.gpa());
return Builder{
.comp = comp,
.parsed_file = parsed_file,
.current_basic_block = undefined,
.code = code,
.next_debug_id = 0,
.is_comptime = false,
.is_async = false,
.begin_scope = begin_scope,
};
}
pub fn abort(self: *Builder) void {
self.code.destroy(self.comp.gpa());
}
/// Call code.destroy() when done
pub fn finish(self: *Builder) *Code {
return self.code;
}
/// No need to clean up resources thanks to the arena allocator.
pub fn createBasicBlock(self: *Builder, scope: *Scope, name_hint: [*]const u8) !*BasicBlock {
const basic_block = try self.arena().create(BasicBlock{
.ref_count = 0,
.name_hint = name_hint,
.debug_id = self.next_debug_id,
.scope = scope,
.instruction_list = std.ArrayList(*Inst).init(self.arena()),
2018-07-13 18:56:38 -07:00
.child = null,
.parent = null,
.ref_instruction = null,
.llvm_block = undefined,
.llvm_exit_block = undefined,
});
self.next_debug_id += 1;
return basic_block;
}
pub fn setCursorAtEndAndAppendBlock(self: *Builder, basic_block: *BasicBlock) !void {
try self.code.basic_block_list.append(basic_block);
self.setCursorAtEnd(basic_block);
}
pub fn setCursorAtEnd(self: *Builder, basic_block: *BasicBlock) void {
self.current_basic_block = basic_block;
}
pub fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Inst {
switch (node.id) {
ast.Node.Id.Root => unreachable,
ast.Node.Id.Use => unreachable,
ast.Node.Id.TestDecl => unreachable,
ast.Node.Id.VarDecl => return error.Unimplemented,
ast.Node.Id.Defer => return error.Unimplemented,
ast.Node.Id.InfixOp => return error.Unimplemented,
ast.Node.Id.PrefixOp => return error.Unimplemented,
ast.Node.Id.SuffixOp => return error.Unimplemented,
ast.Node.Id.Switch => return error.Unimplemented,
ast.Node.Id.While => return error.Unimplemented,
ast.Node.Id.For => return error.Unimplemented,
ast.Node.Id.If => return error.Unimplemented,
ast.Node.Id.ControlFlowExpression => {
const control_flow_expr = @fieldParentPtr(ast.Node.ControlFlowExpression, "base", node);
return irb.genControlFlowExpr(control_flow_expr, scope, lval);
},
ast.Node.Id.Suspend => return error.Unimplemented,
ast.Node.Id.VarType => return error.Unimplemented,
ast.Node.Id.ErrorType => return error.Unimplemented,
ast.Node.Id.FnProto => return error.Unimplemented,
ast.Node.Id.PromiseType => return error.Unimplemented,
ast.Node.Id.IntegerLiteral => return error.Unimplemented,
ast.Node.Id.FloatLiteral => return error.Unimplemented,
ast.Node.Id.StringLiteral => return error.Unimplemented,
ast.Node.Id.MultilineStringLiteral => return error.Unimplemented,
ast.Node.Id.CharLiteral => return error.Unimplemented,
ast.Node.Id.BoolLiteral => return error.Unimplemented,
ast.Node.Id.NullLiteral => return error.Unimplemented,
ast.Node.Id.UndefinedLiteral => return error.Unimplemented,
ast.Node.Id.ThisLiteral => return error.Unimplemented,
ast.Node.Id.Unreachable => return error.Unimplemented,
ast.Node.Id.Identifier => return error.Unimplemented,
ast.Node.Id.GroupedExpression => {
const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", node);
return irb.genNode(grouped_expr.expr, scope, lval);
},
ast.Node.Id.BuiltinCall => return error.Unimplemented,
ast.Node.Id.ErrorSetDecl => return error.Unimplemented,
ast.Node.Id.ContainerDecl => return error.Unimplemented,
ast.Node.Id.Asm => return error.Unimplemented,
ast.Node.Id.Comptime => return error.Unimplemented,
ast.Node.Id.Block => {
const block = @fieldParentPtr(ast.Node.Block, "base", node);
return irb.lvalWrap(scope, try irb.genBlock(block, scope), lval);
},
ast.Node.Id.DocComment => return error.Unimplemented,
ast.Node.Id.SwitchCase => return error.Unimplemented,
ast.Node.Id.SwitchElse => return error.Unimplemented,
ast.Node.Id.Else => return error.Unimplemented,
ast.Node.Id.Payload => return error.Unimplemented,
ast.Node.Id.PointerPayload => return error.Unimplemented,
ast.Node.Id.PointerIndexPayload => return error.Unimplemented,
ast.Node.Id.StructField => return error.Unimplemented,
ast.Node.Id.UnionTag => return error.Unimplemented,
ast.Node.Id.EnumTag => return error.Unimplemented,
ast.Node.Id.ErrorTag => return error.Unimplemented,
ast.Node.Id.AsmInput => return error.Unimplemented,
ast.Node.Id.AsmOutput => return error.Unimplemented,
ast.Node.Id.AsyncAttribute => return error.Unimplemented,
ast.Node.Id.ParamDecl => return error.Unimplemented,
ast.Node.Id.FieldInitializer => return error.Unimplemented,
}
}
fn isCompTime(irb: *Builder, target_scope: *Scope) bool {
if (irb.is_comptime)
return true;
var scope = target_scope;
while (true) {
switch (scope.id) {
Scope.Id.CompTime => return true,
Scope.Id.FnDef => return false,
Scope.Id.Decls => unreachable,
Scope.Id.Block,
Scope.Id.Defer,
Scope.Id.DeferExpr,
=> scope = scope.parent orelse return false,
}
}
}
pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Inst {
const block_scope = try Scope.Block.create(irb.comp, parent_scope);
const outer_block_scope = &block_scope.base;
var child_scope = outer_block_scope;
if (parent_scope.findFnDef()) |fndef_scope| {
if (fndef_scope.fn_val.child_scope == parent_scope) {
fndef_scope.fn_val.block_scope = block_scope;
}
}
if (block.statements.len == 0) {
// {}
2018-07-13 18:56:38 -07:00
return irb.buildConstVoid(child_scope, Span.token(block.lbrace), false);
}
if (block.label) |label| {
block_scope.incoming_values = std.ArrayList(*Inst).init(irb.arena());
block_scope.incoming_blocks = std.ArrayList(*BasicBlock).init(irb.arena());
block_scope.end_block = try irb.createBasicBlock(parent_scope, c"BlockEnd");
2018-07-13 18:56:38 -07:00
block_scope.is_comptime = try irb.buildConstBool(
parent_scope,
Span.token(block.lbrace),
irb.isCompTime(parent_scope),
);
}
var is_continuation_unreachable = false;
var noreturn_return_value: ?*Inst = null;
var stmt_it = block.statements.iterator(0);
while (stmt_it.next()) |statement_node_ptr| {
const statement_node = statement_node_ptr.*;
if (statement_node.cast(ast.Node.Defer)) |defer_node| {
// defer starts a new scope
const defer_token = irb.parsed_file.tree.tokens.at(defer_node.defer_token);
const kind = switch (defer_token.id) {
Token.Id.Keyword_defer => Scope.Defer.Kind.ScopeExit,
Token.Id.Keyword_errdefer => Scope.Defer.Kind.ErrorExit,
else => unreachable,
};
const defer_expr_scope = try Scope.DeferExpr.create(irb.comp, parent_scope, defer_node.expr);
const defer_child_scope = try Scope.Defer.create(irb.comp, parent_scope, kind, defer_expr_scope);
child_scope = &defer_child_scope.base;
continue;
}
const statement_value = try irb.genNode(statement_node, child_scope, LVal.None);
is_continuation_unreachable = statement_value.isNoReturn();
if (is_continuation_unreachable) {
// keep the last noreturn statement value around in case we need to return it
noreturn_return_value = statement_value;
}
if (statement_value.cast(Inst.DeclVar)) |decl_var| {
// variable declarations start a new scope
2018-07-13 18:56:38 -07:00
child_scope = decl_var.params.variable.child_scope;
} else if (!is_continuation_unreachable) {
// this statement's value must be void
2018-07-13 18:56:38 -07:00
_ = irb.build(
Inst.CheckVoidStmt,
2018-07-13 18:56:38 -07:00
child_scope,
statement_value.span,
Inst.CheckVoidStmt.Params{ .target = statement_value },
2018-07-13 18:56:38 -07:00
);
}
}
if (is_continuation_unreachable) {
assert(noreturn_return_value != null);
if (block.label == null or block_scope.incoming_blocks.len == 0) {
return noreturn_return_value.?;
}
try irb.setCursorAtEndAndAppendBlock(block_scope.end_block);
return irb.build(Inst.Phi, parent_scope, Span.token(block.rbrace), Inst.Phi.Params{
2018-07-13 18:56:38 -07:00
.incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(),
.incoming_values = block_scope.incoming_values.toOwnedSlice(),
});
}
if (block.label) |label| {
try block_scope.incoming_blocks.append(irb.current_basic_block);
try block_scope.incoming_values.append(
2018-07-13 18:56:38 -07:00
try irb.buildConstVoid(parent_scope, Span.token(block.rbrace), true),
);
_ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit);
2018-07-13 18:56:38 -07:00
_ = try irb.buildGen(Inst.Br, parent_scope, Span.token(block.rbrace), Inst.Br.Params{
2018-07-13 18:56:38 -07:00
.dest_block = block_scope.end_block,
.is_comptime = block_scope.is_comptime,
});
try irb.setCursorAtEndAndAppendBlock(block_scope.end_block);
2018-07-13 18:56:38 -07:00
return irb.build(Inst.Phi, parent_scope, Span.token(block.rbrace), Inst.Phi.Params{
2018-07-13 18:56:38 -07:00
.incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(),
.incoming_values = block_scope.incoming_values.toOwnedSlice(),
});
}
_ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit);
2018-07-13 18:56:38 -07:00
return irb.buildConstVoid(child_scope, Span.token(block.rbrace), true);
}
pub fn genControlFlowExpr(
irb: *Builder,
control_flow_expr: *ast.Node.ControlFlowExpression,
scope: *Scope,
lval: LVal,
) !*Inst {
switch (control_flow_expr.kind) {
ast.Node.ControlFlowExpression.Kind.Break => |arg| return error.Unimplemented,
ast.Node.ControlFlowExpression.Kind.Continue => |arg| return error.Unimplemented,
ast.Node.ControlFlowExpression.Kind.Return => {
const src_span = Span.token(control_flow_expr.ltoken);
if (scope.findFnDef() == null) {
try irb.comp.addCompileError(
irb.parsed_file,
src_span,
"return expression outside function definition",
);
return error.SemanticAnalysisFailed;
}
if (scope.findDeferExpr()) |scope_defer_expr| {
if (!scope_defer_expr.reported_err) {
try irb.comp.addCompileError(
irb.parsed_file,
src_span,
"cannot return from defer expression",
);
scope_defer_expr.reported_err = true;
}
return error.SemanticAnalysisFailed;
}
const outer_scope = irb.begin_scope.?;
const return_value = if (control_flow_expr.rhs) |rhs| blk: {
break :blk try irb.genNode(rhs, scope, LVal.None);
} else blk: {
break :blk try irb.buildConstVoid(scope, src_span, true);
};
const defer_counts = irb.countDefers(scope, outer_scope);
const have_err_defers = defer_counts.error_exit != 0;
if (have_err_defers or irb.comp.have_err_ret_tracing) {
const err_block = try irb.createBasicBlock(scope, c"ErrRetErr");
const ok_block = try irb.createBasicBlock(scope, c"ErrRetOk");
if (!have_err_defers) {
_ = try irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ScopeExit);
}
const is_err = try irb.build(
Inst.TestErr,
scope,
src_span,
Inst.TestErr.Params{ .target = return_value },
);
const err_is_comptime = try irb.buildTestCompTime(scope, src_span, is_err);
_ = try irb.buildGen(Inst.CondBr, scope, src_span, Inst.CondBr.Params{
.condition = is_err,
.then_block = err_block,
.else_block = ok_block,
.is_comptime = err_is_comptime,
});
const ret_stmt_block = try irb.createBasicBlock(scope, c"RetStmt");
try irb.setCursorAtEndAndAppendBlock(err_block);
if (have_err_defers) {
_ = try irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ErrorExit);
}
if (irb.comp.have_err_ret_tracing and !irb.isCompTime(scope)) {
_ = try irb.build(Inst.SaveErrRetAddr, scope, src_span, Inst.SaveErrRetAddr.Params{});
}
_ = try irb.build(Inst.Br, scope, src_span, Inst.Br.Params{
.dest_block = ret_stmt_block,
.is_comptime = err_is_comptime,
});
try irb.setCursorAtEndAndAppendBlock(ok_block);
if (have_err_defers) {
_ = try irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ScopeExit);
}
_ = try irb.build(Inst.Br, scope, src_span, Inst.Br.Params{
.dest_block = ret_stmt_block,
.is_comptime = err_is_comptime,
});
try irb.setCursorAtEndAndAppendBlock(ret_stmt_block);
return irb.genAsyncReturn(scope, src_span, return_value, false);
} else {
_ = try irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ScopeExit);
return irb.genAsyncReturn(scope, src_span, return_value, false);
}
},
}
}
const DeferCounts = struct {
scope_exit: usize,
error_exit: usize,
};
fn countDefers(irb: *Builder, inner_scope: *Scope, outer_scope: *Scope) DeferCounts {
var result = DeferCounts{ .scope_exit = 0, .error_exit = 0 };
var scope = inner_scope;
while (scope != outer_scope) {
switch (scope.id) {
Scope.Id.Defer => {
const defer_scope = @fieldParentPtr(Scope.Defer, "base", scope);
switch (defer_scope.kind) {
Scope.Defer.Kind.ScopeExit => result.scope_exit += 1,
Scope.Defer.Kind.ErrorExit => result.error_exit += 1,
}
scope = scope.parent orelse break;
},
Scope.Id.FnDef => break,
Scope.Id.CompTime,
Scope.Id.Block,
=> scope = scope.parent orelse break,
Scope.Id.DeferExpr => unreachable,
Scope.Id.Decls => unreachable,
}
}
return result;
}
fn genDefersForBlock(
irb: *Builder,
inner_scope: *Scope,
outer_scope: *Scope,
gen_kind: Scope.Defer.Kind,
) !bool {
var scope = inner_scope;
var is_noreturn = false;
while (true) {
switch (scope.id) {
Scope.Id.Defer => {
const defer_scope = @fieldParentPtr(Scope.Defer, "base", scope);
const generate = switch (defer_scope.kind) {
Scope.Defer.Kind.ScopeExit => true,
Scope.Defer.Kind.ErrorExit => gen_kind == Scope.Defer.Kind.ErrorExit,
};
if (generate) {
const defer_expr_scope = defer_scope.defer_expr_scope;
const instruction = try irb.genNode(
defer_expr_scope.expr_node,
&defer_expr_scope.base,
LVal.None,
);
if (instruction.isNoReturn()) {
is_noreturn = true;
} else {
2018-07-13 18:56:38 -07:00
_ = try irb.build(
Inst.CheckVoidStmt,
2018-07-13 18:56:38 -07:00
&defer_expr_scope.base,
Span.token(defer_expr_scope.expr_node.lastToken()),
Inst.CheckVoidStmt.Params{ .target = instruction },
2018-07-13 18:56:38 -07:00
);
}
}
},
Scope.Id.FnDef,
Scope.Id.Decls,
=> return is_noreturn,
Scope.Id.CompTime,
Scope.Id.Block,
=> scope = scope.parent orelse return is_noreturn,
Scope.Id.DeferExpr => unreachable,
}
}
}
pub fn lvalWrap(irb: *Builder, scope: *Scope, instruction: *Inst, lval: LVal) !*Inst {
switch (lval) {
LVal.None => return instruction,
LVal.Ptr => {
// We needed a pointer to a value, but we got a value. So we create
// an instruction which just makes a const pointer of it.
return irb.build(Inst.Ref, scope, instruction.span, Inst.Ref.Params{
2018-07-13 18:56:38 -07:00
.target = instruction,
.mut = Type.Pointer.Mut.Const,
.volatility = Type.Pointer.Vol.Non,
});
},
}
}
fn arena(self: *Builder) *Allocator {
return &self.code.arena.allocator;
}
2018-07-13 18:56:38 -07:00
fn buildExtra(
self: *Builder,
comptime I: type,
scope: *Scope,
span: Span,
params: I.Params,
is_generated: bool,
) !*Inst {
2018-07-13 18:56:38 -07:00
const inst = try self.arena().create(I{
.base = Inst{
.id = Inst.typeToId(I),
2018-07-13 18:56:38 -07:00
.is_generated = is_generated,
.scope = scope,
.debug_id = self.next_debug_id,
.val = switch (I.ir_val_init) {
IrVal.Init.Unknown => IrVal.Unknown,
IrVal.Init.NoReturn => IrVal{ .KnownValue = &Value.NoReturn.get(self.comp).base },
IrVal.Init.Void => IrVal{ .KnownValue = &Value.Void.get(self.comp).base },
2018-07-13 18:56:38 -07:00
},
.ref_count = 0,
.span = span,
.child = null,
.parent = null,
.llvm_value = undefined,
.owner_bb = self.current_basic_block,
2018-07-13 18:56:38 -07:00
},
.params = params,
});
// Look at the params and ref() other instructions
comptime var i = 0;
inline while (i < @memberCount(I.Params)) : (i += 1) {
const FieldType = comptime @typeOf(@field(I.Params(undefined), @memberName(I.Params, i)));
switch (FieldType) {
*Inst => @field(inst.params, @memberName(I.Params, i)).ref(self),
?*Inst => if (@field(inst.params, @memberName(I.Params, i))) |other| other.ref(self),
2018-07-13 18:56:38 -07:00
else => {},
}
}
self.next_debug_id += 1;
try self.current_basic_block.instruction_list.append(&inst.base);
return &inst.base;
}
fn build(
self: *Builder,
comptime I: type,
scope: *Scope,
span: Span,
params: I.Params,
) !*Inst {
2018-07-13 18:56:38 -07:00
return self.buildExtra(I, scope, span, params, false);
}
fn buildGen(
self: *Builder,
comptime I: type,
scope: *Scope,
span: Span,
params: I.Params,
) !*Inst {
2018-07-13 18:56:38 -07:00
return self.buildExtra(I, scope, span, params, true);
}
fn buildConstBool(self: *Builder, scope: *Scope, span: Span, x: bool) !*Inst {
const inst = try self.build(Inst.Const, scope, span, Inst.Const.Params{});
inst.val = IrVal{ .KnownValue = &Value.Bool.get(self.comp, x).base };
2018-07-13 18:56:38 -07:00
return inst;
}
fn buildConstVoid(self: *Builder, scope: *Scope, span: Span, is_generated: bool) !*Inst {
const inst = try self.buildExtra(Inst.Const, scope, span, Inst.Const.Params{}, is_generated);
inst.val = IrVal{ .KnownValue = &Value.Void.get(self.comp).base };
2018-07-13 18:56:38 -07:00
return inst;
}
/// If the code is explicitly set to be comptime, then builds a const bool,
/// otherwise builds a TestCompTime instruction.
fn buildTestCompTime(self: *Builder, scope: *Scope, span: Span, target: *Inst) !*Inst {
if (self.isCompTime(scope)) {
return self.buildConstBool(scope, span, true);
} else {
return self.build(
Inst.TestCompTime,
scope,
span,
Inst.TestCompTime.Params{ .target = target },
);
}
}
fn genAsyncReturn(irb: *Builder, scope: *Scope, span: Span, result: *Inst, is_gen: bool) !*Inst {
_ = irb.buildGen(
Inst.AddImplicitReturnType,
scope,
span,
Inst.AddImplicitReturnType.Params{ .target = result },
);
if (!irb.is_async) {
return irb.buildExtra(
Inst.Return,
scope,
span,
Inst.Return.Params{ .return_value = result },
is_gen,
);
}
return error.Unimplemented;
//ir_build_store_ptr(irb, scope, node, irb->exec->coro_result_field_ptr, return_value);
//IrInstruction *promise_type_val = ir_build_const_type(irb, scope, node,
// get_optional_type(irb->codegen, irb->codegen->builtin_types.entry_promise));
//// TODO replace replacement_value with @intToPtr(?promise, 0x1) when it doesn't crash zig
//IrInstruction *replacement_value = irb->exec->coro_handle;
//IrInstruction *maybe_await_handle = ir_build_atomic_rmw(irb, scope, node,
// promise_type_val, irb->exec->coro_awaiter_field_ptr, nullptr, replacement_value, nullptr,
// AtomicRmwOp_xchg, AtomicOrderSeqCst);
//ir_build_store_ptr(irb, scope, node, irb->exec->await_handle_var_ptr, maybe_await_handle);
//IrInstruction *is_non_null = ir_build_test_nonnull(irb, scope, node, maybe_await_handle);
//IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, false);
//return ir_build_cond_br(irb, scope, node, is_non_null, irb->exec->coro_normal_final, irb->exec->coro_early_final,
// is_comptime);
//// the above blocks are rendered by ir_gen after the rest of codegen
}
2018-07-13 18:56:38 -07:00
};
const Analyze = struct {
irb: Builder,
old_bb_index: usize,
const_predecessor_bb: ?*BasicBlock,
parent_basic_block: *BasicBlock,
instruction_index: usize,
src_implicit_return_type_list: std.ArrayList(*Inst),
2018-07-13 18:56:38 -07:00
explicit_return_type: ?*Type,
pub const Error = error{
/// This is only for when we have already reported a compile error. It is the poison value.
SemanticAnalysisFailed,
/// This is a placeholder - it is useful to use instead of panicking but once the compiler is
/// done this error code will be removed.
Unimplemented,
OutOfMemory,
};
pub fn init(comp: *Compilation, parsed_file: *ParsedFile, explicit_return_type: ?*Type) !Analyze {
var irb = try Builder.init(comp, parsed_file, null);
2018-07-13 18:56:38 -07:00
errdefer irb.abort();
return Analyze{
.irb = irb,
.old_bb_index = 0,
.const_predecessor_bb = null,
.parent_basic_block = undefined, // initialized with startBasicBlock
.instruction_index = undefined, // initialized with startBasicBlock
.src_implicit_return_type_list = std.ArrayList(*Inst).init(irb.arena()),
2018-07-13 18:56:38 -07:00
.explicit_return_type = explicit_return_type,
};
}
pub fn abort(self: *Analyze) void {
self.irb.abort();
}
pub fn getNewBasicBlock(self: *Analyze, old_bb: *BasicBlock, ref_old_instruction: ?*Inst) !*BasicBlock {
2018-07-13 18:56:38 -07:00
if (old_bb.child) |child| {
if (ref_old_instruction == null or child.ref_instruction != ref_old_instruction)
return child;
}
const new_bb = try self.irb.createBasicBlock(old_bb.scope, old_bb.name_hint);
new_bb.linkToParent(old_bb);
new_bb.ref_instruction = ref_old_instruction;
return new_bb;
}
pub fn startBasicBlock(self: *Analyze, old_bb: *BasicBlock, const_predecessor_bb: ?*BasicBlock) void {
self.instruction_index = 0;
self.parent_basic_block = old_bb;
self.const_predecessor_bb = const_predecessor_bb;
}
pub fn finishBasicBlock(ira: *Analyze, old_code: *Code) !void {
try ira.irb.code.basic_block_list.append(ira.irb.current_basic_block);
ira.instruction_index += 1;
while (ira.instruction_index < ira.parent_basic_block.instruction_list.len) {
const next_instruction = ira.parent_basic_block.instruction_list.at(ira.instruction_index);
if (!next_instruction.is_generated) {
try ira.addCompileError(next_instruction.span, "unreachable code");
break;
}
ira.instruction_index += 1;
}
ira.old_bb_index += 1;
var need_repeat = true;
while (true) {
while (ira.old_bb_index < old_code.basic_block_list.len) {
const old_bb = old_code.basic_block_list.at(ira.old_bb_index);
const new_bb = old_bb.child orelse {
ira.old_bb_index += 1;
continue;
};
if (new_bb.instruction_list.len != 0) {
ira.old_bb_index += 1;
continue;
}
ira.irb.current_basic_block = new_bb;
ira.startBasicBlock(old_bb, null);
return;
}
if (!need_repeat)
return;
need_repeat = false;
ira.old_bb_index = 0;
continue;
}
}
fn addCompileError(self: *Analyze, span: Span, comptime fmt: []const u8, args: ...) !void {
return self.irb.comp.addCompileError(self.irb.parsed_file, span, fmt, args);
2018-07-13 18:56:38 -07:00
}
fn resolvePeerTypes(self: *Analyze, expected_type: ?*Type, peers: []const *Inst) Analyze.Error!*Type {
2018-07-13 18:56:38 -07:00
// TODO actual implementation
return &Type.Void.get(self.irb.comp).base;
2018-07-13 18:56:38 -07:00
}
fn implicitCast(self: *Analyze, target: *Inst, optional_dest_type: ?*Type) Analyze.Error!*Inst {
2018-07-13 18:56:38 -07:00
const dest_type = optional_dest_type orelse return target;
return error.Unimplemented;
2018-07-13 18:56:38 -07:00
}
fn getCompTimeValOrNullUndefOk(self: *Analyze, target: *Inst) ?*Value {
@panic("TODO");
2018-07-13 18:56:38 -07:00
}
fn getCompTimeRef(
self: *Analyze,
value: *Value,
ptr_mut: Value.Ptr.Mut,
mut: Type.Pointer.Mut,
volatility: Type.Pointer.Vol,
ptr_align: u32,
) Analyze.Error!*Inst {
return error.Unimplemented;
2018-07-13 18:56:38 -07:00
}
};
2018-07-13 18:56:38 -07:00
pub async fn gen(
comp: *Compilation,
2018-07-13 18:56:38 -07:00
body_node: *ast.Node,
scope: *Scope,
parsed_file: *ParsedFile,
) !*Code {
var irb = try Builder.init(comp, parsed_file, scope);
errdefer irb.abort();
const entry_block = try irb.createBasicBlock(scope, c"Entry");
entry_block.ref(); // Entry block gets a reference because we enter it to begin.
try irb.setCursorAtEndAndAppendBlock(entry_block);
const result = try irb.genNode(body_node, scope, LVal.None);
if (!result.isNoReturn()) {
// no need for save_err_ret_addr because this cannot return error
_ = try irb.genAsyncReturn(scope, Span.token(body_node.lastToken()), result, true);
}
return irb.finish();
}
2018-07-13 18:56:38 -07:00
pub async fn analyze(comp: *Compilation, parsed_file: *ParsedFile, old_code: *Code, expected_type: ?*Type) !*Code {
var ira = try Analyze.init(comp, parsed_file, expected_type);
2018-07-13 18:56:38 -07:00
errdefer ira.abort();
const old_entry_bb = old_code.basic_block_list.at(0);
const new_entry_bb = try ira.getNewBasicBlock(old_entry_bb, null);
new_entry_bb.ref();
ira.irb.current_basic_block = new_entry_bb;
ira.startBasicBlock(old_entry_bb, null);
while (ira.old_bb_index < old_code.basic_block_list.len) {
const old_instruction = ira.parent_basic_block.instruction_list.at(ira.instruction_index);
if (old_instruction.ref_count == 0 and !old_instruction.hasSideEffects()) {
ira.instruction_index += 1;
continue;
}
const return_inst = try old_instruction.analyze(&ira);
return_inst.linkToParent(old_instruction);
2018-07-13 18:56:38 -07:00
// Note: if we ever modify the above to handle error.CompileError by continuing analysis,
// then here we want to check if ira.isCompTime() and return early if true
if (return_inst.isNoReturn()) {
try ira.finishBasicBlock(old_code);
continue;
}
ira.instruction_index += 1;
}
if (ira.src_implicit_return_type_list.len == 0) {
ira.irb.code.return_type = &Type.NoReturn.get(comp).base;
2018-07-13 18:56:38 -07:00
return ira.irb.finish();
}
ira.irb.code.return_type = try ira.resolvePeerTypes(expected_type, ira.src_implicit_return_type_list.toSliceConst());
return ira.irb.finish();
}