mirror of
https://github.com/ziglang/zig.git
synced 2026-01-03 20:13:21 +00:00
654 lines
22 KiB
Zig
654 lines
22 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const Module = @import("module.zig").Module;
|
|
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;
|
|
|
|
pub const LVal = enum {
|
|
None,
|
|
Ptr,
|
|
};
|
|
|
|
pub const Mut = enum {
|
|
Mut,
|
|
Const,
|
|
};
|
|
|
|
pub const Volatility = enum {
|
|
NonVolatile,
|
|
Volatile,
|
|
};
|
|
|
|
pub const IrVal = union(enum) {
|
|
Unknown,
|
|
Known: *Value,
|
|
|
|
pub fn dump(self: IrVal) void {
|
|
switch (self) {
|
|
IrVal.Unknown => std.debug.warn("Unknown"),
|
|
IrVal.Known => |value| {
|
|
std.debug.warn("Known(");
|
|
value.dump();
|
|
std.debug.warn(")");
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const Instruction = struct {
|
|
id: Id,
|
|
scope: *Scope,
|
|
debug_id: usize,
|
|
val: IrVal,
|
|
|
|
/// true if this instruction was generated by zig and not from user code
|
|
is_generated: bool,
|
|
|
|
pub fn cast(base: *Instruction, 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(Instruction, @memberName(Id, i))) {
|
|
return @field(Id, @memberName(Id, i));
|
|
}
|
|
}
|
|
unreachable;
|
|
}
|
|
|
|
pub fn dump(base: *const Instruction) void {
|
|
comptime var i = 0;
|
|
inline while (i < @memberCount(Id)) : (i += 1) {
|
|
if (base.id == @field(Id, @memberName(Id, i))) {
|
|
const T = @field(Instruction, @memberName(Id, i));
|
|
std.debug.warn("#{} = {}(", base.debug_id, @tagName(base.id));
|
|
@fieldParentPtr(T, "base", base).dump();
|
|
std.debug.warn(")");
|
|
return;
|
|
}
|
|
}
|
|
unreachable;
|
|
}
|
|
|
|
pub fn setGenerated(base: *Instruction) void {
|
|
base.is_generated = true;
|
|
}
|
|
|
|
pub fn isNoReturn(base: *const Instruction) bool {
|
|
switch (base.val) {
|
|
IrVal.Unknown => return false,
|
|
IrVal.Known => |x| return x.typeof.id == Type.Id.NoReturn,
|
|
}
|
|
}
|
|
|
|
pub const Id = enum {
|
|
Return,
|
|
Const,
|
|
Ref,
|
|
DeclVar,
|
|
CheckVoidStmt,
|
|
Phi,
|
|
Br,
|
|
};
|
|
|
|
pub const Const = struct {
|
|
base: Instruction,
|
|
|
|
pub fn buildBool(irb: *Builder, scope: *Scope, val: bool) !*Instruction {
|
|
const inst = try irb.arena().create(Const{
|
|
.base = Instruction{
|
|
.id = Instruction.Id.Const,
|
|
.is_generated = false,
|
|
.scope = scope,
|
|
.debug_id = irb.next_debug_id,
|
|
.val = IrVal{ .Known = &Value.Bool.get(irb.module, val).base },
|
|
},
|
|
});
|
|
irb.next_debug_id += 1;
|
|
try irb.current_basic_block.instruction_list.append(&inst.base);
|
|
return &inst.base;
|
|
}
|
|
|
|
pub fn buildVoid(irb: *Builder, scope: *Scope, is_generated: bool) !*Instruction {
|
|
const inst = try irb.arena().create(Const{
|
|
.base = Instruction{
|
|
.id = Instruction.Id.Const,
|
|
.is_generated = is_generated,
|
|
.scope = scope,
|
|
.debug_id = irb.next_debug_id,
|
|
.val = IrVal{ .Known = &Value.Void.get(irb.module).base },
|
|
},
|
|
});
|
|
irb.next_debug_id += 1;
|
|
try irb.current_basic_block.instruction_list.append(&inst.base);
|
|
return &inst.base;
|
|
}
|
|
|
|
pub fn dump(inst: *const Const) void {
|
|
inst.base.val.Known.dump();
|
|
}
|
|
};
|
|
|
|
pub const Return = struct {
|
|
base: Instruction,
|
|
return_value: *Instruction,
|
|
|
|
pub fn build(irb: *Builder, scope: *Scope, return_value: *Instruction) !*Instruction {
|
|
const inst = try irb.arena().create(Return{
|
|
.base = Instruction{
|
|
.id = Instruction.Id.Return,
|
|
.is_generated = false,
|
|
.scope = scope,
|
|
.debug_id = irb.next_debug_id,
|
|
.val = IrVal{ .Known = &Value.Void.get(irb.module).base },
|
|
},
|
|
.return_value = return_value,
|
|
});
|
|
irb.next_debug_id += 1;
|
|
try irb.current_basic_block.instruction_list.append(&inst.base);
|
|
return &inst.base;
|
|
}
|
|
|
|
pub fn dump(inst: *const Return) void {
|
|
std.debug.warn("#{}", inst.return_value.debug_id);
|
|
}
|
|
};
|
|
|
|
pub const Ref = struct {
|
|
base: Instruction,
|
|
target: *Instruction,
|
|
mut: Mut,
|
|
volatility: Volatility,
|
|
|
|
pub fn build(
|
|
irb: *Builder,
|
|
scope: *Scope,
|
|
target: *Instruction,
|
|
mut: Mut,
|
|
volatility: Volatility,
|
|
) !*Instruction {
|
|
const inst = try irb.arena().create(Ref{
|
|
.base = Instruction{
|
|
.id = Instruction.Id.Ref,
|
|
.is_generated = false,
|
|
.scope = scope,
|
|
.debug_id = irb.next_debug_id,
|
|
.val = IrVal.Unknown,
|
|
},
|
|
.target = target,
|
|
.mut = mut,
|
|
.volatility = volatility,
|
|
});
|
|
irb.next_debug_id += 1;
|
|
try irb.current_basic_block.instruction_list.append(&inst.base);
|
|
return &inst.base;
|
|
}
|
|
|
|
pub fn dump(inst: *const Ref) void {}
|
|
};
|
|
|
|
pub const DeclVar = struct {
|
|
base: Instruction,
|
|
variable: *Variable,
|
|
|
|
pub fn dump(inst: *const DeclVar) void {}
|
|
};
|
|
|
|
pub const CheckVoidStmt = struct {
|
|
base: Instruction,
|
|
target: *Instruction,
|
|
|
|
pub fn build(
|
|
irb: *Builder,
|
|
scope: *Scope,
|
|
target: *Instruction,
|
|
) !*Instruction {
|
|
const inst = try irb.arena().create(CheckVoidStmt{
|
|
.base = Instruction{
|
|
.id = Instruction.Id.CheckVoidStmt,
|
|
.is_generated = true,
|
|
.scope = scope,
|
|
.debug_id = irb.next_debug_id,
|
|
.val = IrVal{ .Known = &Value.Void.get(irb.module).base },
|
|
},
|
|
.target = target,
|
|
});
|
|
irb.next_debug_id += 1;
|
|
try irb.current_basic_block.instruction_list.append(&inst.base);
|
|
return &inst.base;
|
|
}
|
|
|
|
pub fn dump(inst: *const CheckVoidStmt) void {}
|
|
};
|
|
|
|
pub const Phi = struct {
|
|
base: Instruction,
|
|
incoming_blocks: []*BasicBlock,
|
|
incoming_values: []*Instruction,
|
|
|
|
pub fn build(
|
|
irb: *Builder,
|
|
scope: *Scope,
|
|
incoming_blocks: []*BasicBlock,
|
|
incoming_values: []*Instruction,
|
|
) !*Instruction {
|
|
const inst = try irb.arena().create(Phi{
|
|
.base = Instruction{
|
|
.id = Instruction.Id.Phi,
|
|
.is_generated = false,
|
|
.scope = scope,
|
|
.debug_id = irb.next_debug_id,
|
|
.val = IrVal.Unknown,
|
|
},
|
|
.incoming_blocks = incoming_blocks,
|
|
.incoming_values = incoming_values,
|
|
});
|
|
irb.next_debug_id += 1;
|
|
try irb.current_basic_block.instruction_list.append(&inst.base);
|
|
return &inst.base;
|
|
}
|
|
|
|
pub fn dump(inst: *const Phi) void {}
|
|
};
|
|
|
|
pub const Br = struct {
|
|
base: Instruction,
|
|
dest_block: *BasicBlock,
|
|
is_comptime: *Instruction,
|
|
|
|
pub fn build(
|
|
irb: *Builder,
|
|
scope: *Scope,
|
|
dest_block: *BasicBlock,
|
|
is_comptime: *Instruction,
|
|
) !*Instruction {
|
|
const inst = try irb.arena().create(Br{
|
|
.base = Instruction{
|
|
.id = Instruction.Id.Br,
|
|
.is_generated = false,
|
|
.scope = scope,
|
|
.debug_id = irb.next_debug_id,
|
|
.val = IrVal{ .Known = &Value.NoReturn.get(irb.module).base },
|
|
},
|
|
.dest_block = dest_block,
|
|
.is_comptime = is_comptime,
|
|
});
|
|
irb.next_debug_id += 1;
|
|
try irb.current_basic_block.instruction_list.append(&inst.base);
|
|
return &inst.base;
|
|
}
|
|
|
|
pub fn dump(inst: *const Br) void {}
|
|
};
|
|
};
|
|
|
|
pub const Variable = struct {
|
|
child_scope: *Scope,
|
|
};
|
|
|
|
pub const BasicBlock = struct {
|
|
ref_count: usize,
|
|
name_hint: []const u8,
|
|
debug_id: usize,
|
|
scope: *Scope,
|
|
instruction_list: std.ArrayList(*Instruction),
|
|
|
|
pub fn ref(self: *BasicBlock) void {
|
|
self.ref_count += 1;
|
|
}
|
|
};
|
|
|
|
/// Stuff that survives longer than Builder
|
|
pub const Code = struct {
|
|
basic_block_list: std.ArrayList(*BasicBlock),
|
|
arena: std.heap.ArenaAllocator,
|
|
|
|
/// allocator is module.a()
|
|
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("{}_{}:\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 {
|
|
module: *Module,
|
|
code: *Code,
|
|
current_basic_block: *BasicBlock,
|
|
next_debug_id: usize,
|
|
parsed_file: *ParsedFile,
|
|
is_comptime: bool,
|
|
|
|
pub const Error = error{
|
|
OutOfMemory,
|
|
Unimplemented,
|
|
};
|
|
|
|
pub fn init(module: *Module, parsed_file: *ParsedFile) !Builder {
|
|
const code = try module.a().create(Code{
|
|
.basic_block_list = undefined,
|
|
.arena = std.heap.ArenaAllocator.init(module.a()),
|
|
});
|
|
code.basic_block_list = std.ArrayList(*BasicBlock).init(&code.arena.allocator);
|
|
errdefer code.destroy(module.a());
|
|
|
|
return Builder{
|
|
.module = module,
|
|
.parsed_file = parsed_file,
|
|
.current_basic_block = undefined,
|
|
.code = code,
|
|
.next_debug_id = 0,
|
|
.is_comptime = false,
|
|
};
|
|
}
|
|
|
|
pub fn abort(self: *Builder) void {
|
|
self.code.destroy(self.module.a());
|
|
}
|
|
|
|
/// 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(*Instruction).init(self.arena()),
|
|
});
|
|
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!*Instruction {
|
|
switch (node.id) {
|
|
ast.Node.Id.Root => unreachable,
|
|
ast.Node.Id.Use => unreachable,
|
|
ast.Node.Id.TestDecl => unreachable,
|
|
ast.Node.Id.VarDecl => @panic("TODO"),
|
|
ast.Node.Id.Defer => @panic("TODO"),
|
|
ast.Node.Id.InfixOp => @panic("TODO"),
|
|
ast.Node.Id.PrefixOp => @panic("TODO"),
|
|
ast.Node.Id.SuffixOp => @panic("TODO"),
|
|
ast.Node.Id.Switch => @panic("TODO"),
|
|
ast.Node.Id.While => @panic("TODO"),
|
|
ast.Node.Id.For => @panic("TODO"),
|
|
ast.Node.Id.If => @panic("TODO"),
|
|
ast.Node.Id.ControlFlowExpression => return error.Unimplemented,
|
|
ast.Node.Id.Suspend => @panic("TODO"),
|
|
ast.Node.Id.VarType => @panic("TODO"),
|
|
ast.Node.Id.ErrorType => @panic("TODO"),
|
|
ast.Node.Id.FnProto => @panic("TODO"),
|
|
ast.Node.Id.PromiseType => @panic("TODO"),
|
|
ast.Node.Id.IntegerLiteral => @panic("TODO"),
|
|
ast.Node.Id.FloatLiteral => @panic("TODO"),
|
|
ast.Node.Id.StringLiteral => @panic("TODO"),
|
|
ast.Node.Id.MultilineStringLiteral => @panic("TODO"),
|
|
ast.Node.Id.CharLiteral => @panic("TODO"),
|
|
ast.Node.Id.BoolLiteral => @panic("TODO"),
|
|
ast.Node.Id.NullLiteral => @panic("TODO"),
|
|
ast.Node.Id.UndefinedLiteral => @panic("TODO"),
|
|
ast.Node.Id.ThisLiteral => @panic("TODO"),
|
|
ast.Node.Id.Unreachable => @panic("TODO"),
|
|
ast.Node.Id.Identifier => @panic("TODO"),
|
|
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 => @panic("TODO"),
|
|
ast.Node.Id.ErrorSetDecl => @panic("TODO"),
|
|
ast.Node.Id.ContainerDecl => @panic("TODO"),
|
|
ast.Node.Id.Asm => @panic("TODO"),
|
|
ast.Node.Id.Comptime => @panic("TODO"),
|
|
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 => @panic("TODO"),
|
|
ast.Node.Id.SwitchCase => @panic("TODO"),
|
|
ast.Node.Id.SwitchElse => @panic("TODO"),
|
|
ast.Node.Id.Else => @panic("TODO"),
|
|
ast.Node.Id.Payload => @panic("TODO"),
|
|
ast.Node.Id.PointerPayload => @panic("TODO"),
|
|
ast.Node.Id.PointerIndexPayload => @panic("TODO"),
|
|
ast.Node.Id.StructField => @panic("TODO"),
|
|
ast.Node.Id.UnionTag => @panic("TODO"),
|
|
ast.Node.Id.EnumTag => @panic("TODO"),
|
|
ast.Node.Id.ErrorTag => @panic("TODO"),
|
|
ast.Node.Id.AsmInput => @panic("TODO"),
|
|
ast.Node.Id.AsmOutput => @panic("TODO"),
|
|
ast.Node.Id.AsyncAttribute => @panic("TODO"),
|
|
ast.Node.Id.ParamDecl => @panic("TODO"),
|
|
ast.Node.Id.FieldInitializer => @panic("TODO"),
|
|
}
|
|
}
|
|
|
|
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) !*Instruction {
|
|
const block_scope = try Scope.Block.create(irb.module, 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) {
|
|
// {}
|
|
return Instruction.Const.buildVoid(irb, child_scope, false);
|
|
}
|
|
|
|
if (block.label) |label| {
|
|
block_scope.incoming_values = std.ArrayList(*Instruction).init(irb.arena());
|
|
block_scope.incoming_blocks = std.ArrayList(*BasicBlock).init(irb.arena());
|
|
block_scope.end_block = try irb.createBasicBlock(parent_scope, "BlockEnd");
|
|
block_scope.is_comptime = try Instruction.Const.buildBool(irb, parent_scope, irb.isCompTime(parent_scope));
|
|
}
|
|
|
|
var is_continuation_unreachable = false;
|
|
var noreturn_return_value: ?*Instruction = 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.module, parent_scope, defer_node.expr);
|
|
const defer_child_scope = try Scope.Defer.create(irb.module, 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(Instruction.DeclVar)) |decl_var| {
|
|
// variable declarations start a new scope
|
|
child_scope = decl_var.variable.child_scope;
|
|
} else if (!is_continuation_unreachable) {
|
|
// this statement's value must be void
|
|
_ = Instruction.CheckVoidStmt.build(irb, child_scope, statement_value);
|
|
}
|
|
}
|
|
|
|
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 Instruction.Phi.build(
|
|
irb,
|
|
parent_scope,
|
|
block_scope.incoming_blocks.toOwnedSlice(),
|
|
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(
|
|
try Instruction.Const.buildVoid(irb, parent_scope, true),
|
|
);
|
|
_ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit);
|
|
(try Instruction.Br.build(
|
|
irb,
|
|
parent_scope,
|
|
block_scope.end_block,
|
|
block_scope.is_comptime,
|
|
)).setGenerated();
|
|
try irb.setCursorAtEndAndAppendBlock(block_scope.end_block);
|
|
return Instruction.Phi.build(
|
|
irb,
|
|
parent_scope,
|
|
block_scope.incoming_blocks.toOwnedSlice(),
|
|
block_scope.incoming_values.toOwnedSlice(),
|
|
);
|
|
}
|
|
|
|
_ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit);
|
|
return try Instruction.Const.buildVoid(irb, child_scope, true);
|
|
}
|
|
|
|
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 {
|
|
_ = Instruction.CheckVoidStmt.build(irb, &defer_expr_scope.base, instruction);
|
|
}
|
|
}
|
|
},
|
|
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: *Instruction, lval: LVal) !*Instruction {
|
|
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 Instruction.Ref.build(irb, scope, instruction, Mut.Const, Volatility.NonVolatile);
|
|
},
|
|
}
|
|
}
|
|
|
|
fn arena(self: *Builder) *Allocator {
|
|
return &self.code.arena.allocator;
|
|
}
|
|
};
|
|
|
|
pub async fn gen(module: *Module, body_node: *ast.Node, scope: *Scope, parsed_file: *ParsedFile) !*Code {
|
|
var irb = try Builder.init(module, parsed_file);
|
|
errdefer irb.abort();
|
|
|
|
const entry_block = try irb.createBasicBlock(scope, "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()) {
|
|
(try Instruction.Return.build(&irb, scope, result)).setGenerated();
|
|
}
|
|
|
|
return irb.finish();
|
|
}
|