stage2: Sema improvements and boolean logic astgen

* add `Module.setBlockBody` and related functions
 * redo astgen for `and` and `or` to use fewer ZIR instructions and
   require less processing for comptime known values
 * Sema: rework `analyzeBody` function. See the new doc comments in this
   commit. Divides ZIR instructions up into 3 categories:
   - always noreturn
   - never noreturn
   - sometimes noreturn
This commit is contained in:
Andrew Kelley 2021-03-22 17:12:52 -07:00
parent 9f0b9b8da1
commit 2f391df2a7
4 changed files with 512 additions and 415 deletions

View File

@ -477,7 +477,7 @@ pub const Scope = struct {
switch (scope.tag) {
.file => return &scope.cast(File).?.tree,
.block => return &scope.cast(Block).?.src_decl.container.file_scope.tree,
.gen_zir => return &scope.cast(GenZir).?.zir_code.decl.container.file_scope.tree,
.gen_zir => return scope.cast(GenZir).?.tree(),
.local_val => return &scope.cast(LocalVal).?.gen_zir.zir_code.decl.container.file_scope.tree,
.local_ptr => return &scope.cast(LocalPtr).?.gen_zir.zir_code.decl.container.file_scope.tree,
.container => return &scope.cast(Container).?.file_scope.tree,
@ -983,6 +983,30 @@ pub const Scope = struct {
return gz.zir_code.decl.nodeSrcLoc(node_index);
}
pub fn tree(gz: *const GenZir) *const ast.Tree {
return &gz.zir_code.decl.container.file_scope.tree;
}
pub fn setBoolBrBody(gz: GenZir, inst: zir.Inst.Index) !void {
try gz.zir_code.extra.ensureCapacity(gz.zir_code.gpa, gz.zir_code.extra.items.len +
@typeInfo(zir.Inst.Block).Struct.fields.len + gz.instructions.items.len);
const zir_datas = gz.zir_code.instructions.items(.data);
zir_datas[inst].bool_br.payload_index = gz.zir_code.addExtraAssumeCapacity(
zir.Inst.Block{ .body_len = @intCast(u32, gz.instructions.items.len) },
);
gz.zir_code.extra.appendSliceAssumeCapacity(gz.instructions.items);
}
pub fn setBlockBody(gz: GenZir, inst: zir.Inst.Index) !void {
try gz.zir_code.extra.ensureCapacity(gz.zir_code.gpa, gz.zir_code.extra.items.len +
@typeInfo(zir.Inst.Block).Struct.fields.len + gz.instructions.items.len);
const zir_datas = gz.zir_code.instructions.items(.data);
zir_datas[inst].pl_node.payload_index = gz.zir_code.addExtraAssumeCapacity(
zir.Inst.Block{ .body_len = @intCast(u32, gz.instructions.items.len) },
);
gz.zir_code.extra.appendSliceAssumeCapacity(gz.instructions.items);
}
pub fn addFnTypeCc(gz: *GenZir, tag: zir.Inst.Tag, args: struct {
param_types: []const zir.Inst.Ref,
ret_ty: zir.Inst.Ref,
@ -1044,41 +1068,6 @@ pub const Scope = struct {
return new_index + gz.zir_code.ref_start_index;
}
pub fn addCondBr(
gz: *GenZir,
condition: zir.Inst.Ref,
then_body: []const zir.Inst.Ref,
else_body: []const zir.Inst.Ref,
/// Absolute node index. This function does the conversion to offset from Decl.
abs_node_index: ast.Node.Index,
) !zir.Inst.Ref {
const gpa = gz.zir_code.gpa;
try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
try gz.zir_code.instructions.ensureCapacity(gpa, gz.zir_code.instructions.len + 1);
try gz.zir_code.extra.ensureCapacity(gpa, gz.zir_code.extra.items.len +
@typeInfo(zir.Inst.CondBr).Struct.fields.len + then_body.len + else_body.len);
const payload_index = gz.zir_code.addExtraAssumeCapacity(zir.Inst.CondBr{
.condition = condition,
.then_body_len = @intCast(u32, then_body.len),
.else_body_len = @intCast(u32, else_body.len),
});
gz.zir_code.extra.appendSliceAssumeCapacity(then_body);
gz.zir_code.extra.appendSliceAssumeCapacity(else_body);
const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len);
gz.zir_code.instructions.appendAssumeCapacity(.{
.tag = .condbr,
.data = .{ .pl_node = .{
.src_node = gz.zir_code.decl.nodeIndexToRelative(abs_node_index),
.payload_index = payload_index,
} },
});
gz.instructions.appendAssumeCapacity(new_index);
return new_index + gz.zir_code.ref_start_index;
}
pub fn addCall(
gz: *GenZir,
tag: zir.Inst.Tag,
@ -1113,6 +1102,30 @@ pub const Scope = struct {
return new_index + gz.zir_code.ref_start_index;
}
/// Note that this returns a `zir.Inst.Index` not a ref.
/// Leaves the `payload_index` field undefined.
pub fn addBoolBr(
gz: *GenZir,
tag: zir.Inst.Tag,
lhs: zir.Inst.Ref,
) !zir.Inst.Index {
assert(lhs != 0);
const gpa = gz.zir_code.gpa;
try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
try gz.zir_code.instructions.ensureCapacity(gpa, gz.zir_code.instructions.len + 1);
const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len);
gz.zir_code.instructions.appendAssumeCapacity(.{
.tag = tag,
.data = .{ .bool_br = .{
.lhs = lhs,
.payload_index = undefined,
} },
});
gz.instructions.appendAssumeCapacity(new_index);
return new_index;
}
pub fn addInt(gz: *GenZir, integer: u64) !zir.Inst.Ref {
return gz.add(.{
.tag = .int,
@ -1291,6 +1304,20 @@ pub const Scope = struct {
return new_index;
}
/// Note that this returns a `zir.Inst.Index` not a ref.
/// Leaves the `payload_index` field undefined.
pub fn addCondBr(gz: *GenZir, node: ast.Node.Index) !zir.Inst.Index {
const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len);
try gz.zir_code.instructions.append(gz.zir_code.gpa, .{
.tag = .condbr,
.data = .{ .pl_node = .{
.src_node = gz.zir_code.decl.nodeIndexToRelative(node),
.payload_index = undefined,
} },
});
return new_index;
}
pub fn add(gz: *GenZir, inst: zir.Inst) !zir.Inst.Ref {
const gpa = gz.zir_code.gpa;
try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
@ -1409,9 +1436,9 @@ pub const WipZirCode = struct {
.bitcast_result_ptr,
.bit_or,
.block,
.block_flat,
.block_comptime,
.block_comptime_flat,
.bool_br_and,
.bool_br_or,
.bool_not,
.bool_and,
.bool_or,
@ -1461,9 +1488,6 @@ pub const WipZirCode = struct {
.ret_type,
.shl,
.shr,
.store,
.store_to_block_ptr,
.store_to_inferred_ptr,
.str,
.sub,
.subwrap,
@ -1497,7 +1521,6 @@ pub const WipZirCode = struct {
.slice_sentinel,
.import,
.typeof_peer,
.resolve_inferred_alloc,
=> return false,
.breakpoint,
@ -1509,6 +1532,7 @@ pub const WipZirCode = struct {
.ensure_err_payload_void,
.@"break",
.break_void_tok,
.break_flat,
.condbr,
.compile_error,
.ret_node,
@ -1517,6 +1541,10 @@ pub const WipZirCode = struct {
.@"unreachable",
.loop,
.elided,
.store,
.store_to_block_ptr,
.store_to_inferred_ptr,
.resolve_inferred_alloc,
=> return true,
}
}
@ -2150,7 +2178,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool {
};
defer block_scope.instructions.deinit(mod.gpa);
try sema.root(&block_scope);
_ = try sema.root(&block_scope);
decl.analysis = .complete;
decl.generation = mod.generation;
@ -2338,6 +2366,7 @@ fn astgenAndSemaFn(
const tag: zir.Inst.Tag = if (is_var_args) .fn_type_var_args else .fn_type;
break :fn_type try fn_type_scope.addFnType(tag, return_type_inst, param_types);
};
_ = try fn_type_scope.addUnNode(.break_flat, fn_type_inst, 0);
// We need the memory for the Type to go into the arena for the Decl
var decl_arena = std.heap.ArenaAllocator.init(mod.gpa);
@ -2370,7 +2399,7 @@ fn astgenAndSemaFn(
};
defer block_scope.instructions.deinit(mod.gpa);
const fn_type = try fn_type_sema.rootAsType(&block_scope, fn_type_inst);
const fn_type = try fn_type_sema.rootAsType(&block_scope);
if (body_node == 0) {
if (!is_extern) {
return mod.failNode(&block_scope.base, fn_proto.ast.fn_token, "non-extern function has no body", .{});
@ -2650,6 +2679,7 @@ fn astgenAndSemaVarDecl(
init_result_loc,
var_decl.ast.init_node,
);
_ = try gen_scope.addUnNode(.break_flat, init_inst, var_decl.ast.init_node);
var code = try gen_scope.finish();
defer code.deinit(mod.gpa);
if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
@ -2676,10 +2706,9 @@ fn astgenAndSemaVarDecl(
};
defer block_scope.instructions.deinit(mod.gpa);
try sema.root(&block_scope);
const init_inst_zir_ref = try sema.root(&block_scope);
// The result location guarantees the type coercion.
const analyzed_init_inst = try sema.resolveInst(init_inst);
const analyzed_init_inst = try sema.resolveInst(init_inst_zir_ref);
// The is_comptime in the Scope.Block guarantees the result is comptime-known.
const val = analyzed_init_inst.value().?;
@ -2713,6 +2742,8 @@ fn astgenAndSemaVarDecl(
defer type_scope.instructions.deinit(mod.gpa);
const var_type = try astgen.typeExpr(mod, &type_scope.base, var_decl.ast.type_node);
_ = try type_scope.addUnNode(.break_flat, var_type, 0);
var code = try type_scope.finish();
defer code.deinit(mod.gpa);
if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
@ -2739,7 +2770,7 @@ fn astgenAndSemaVarDecl(
};
defer block_scope.instructions.deinit(mod.gpa);
const ty = try sema.rootAsType(&block_scope, var_type);
const ty = try sema.rootAsType(&block_scope);
break :vi .{
.ty = try ty.copy(&decl_arena.allocator),
@ -3328,7 +3359,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) !void {
func.state = .in_progress;
log.debug("set {s} to in_progress", .{decl.name});
try sema.root(&inner_block);
_ = try sema.root(&inner_block);
const instructions = try arena.allocator.dupe(*ir.Inst, inner_block.instructions.items);
func.state = .success;

View File

@ -53,172 +53,230 @@ const InnerError = Module.InnerError;
const Decl = Module.Decl;
const LazySrcLoc = Module.LazySrcLoc;
pub fn root(sema: *Sema, root_block: *Scope.Block) !void {
pub fn root(sema: *Sema, root_block: *Scope.Block) !zir.Inst.Ref {
const root_body = sema.code.extra[sema.code.root_start..][0..sema.code.root_len];
return sema.analyzeBody(root_block, root_body);
}
pub fn rootAsType(sema: *Sema, root_block: *Scope.Block, result_inst: zir.Inst.Ref) !Type {
const root_body = sema.code.extra[sema.code.root_start..][0..sema.code.root_len];
try sema.analyzeBody(root_block, root_body);
/// Assumes that `root_block` ends with `break_flat`.
pub fn rootAsType(sema: *Sema, root_block: *Scope.Block) !Type {
const zir_inst_ref = try sema.root(root_block);
// Source location is unneeded because resolveConstValue must have already
// been successfully called when coercing the value to a type, from the
// result location.
return sema.resolveType(root_block, .unneeded, result_inst);
return sema.resolveType(root_block, .unneeded, zir_inst_ref);
}
pub fn analyzeBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Index) !void {
const tracy = trace(@src());
defer tracy.end();
/// ZIR instructions which are always `noreturn` return this. This matches the
/// return type of `analyzeBody` so that we can tail call them.
/// Only appropriate to return when the instruction is known to be NoReturn
/// solely based on the ZIR tag.
const always_noreturn: InnerError!zir.Inst.Ref = @as(zir.Inst.Index, 0);
/// This function is the main loop of `Sema` and it can be used in two different ways:
/// * The traditional way where there are N breaks out of the block and peer type
/// resolution is done on the break operands. In this case, the `zir.Inst.Index`
/// part of the return value will be `undefined`, and callsites should ignore it,
/// finding the block result value via the block scope.
/// * The "flat" way. There is only 1 break out of the block, and it is with a `break_flat`
/// instruction. In this case, the `zir.Inst.Index` part of the return value will be
/// the block result value. No block scope needs to be created for this strategy.
pub fn analyzeBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Index) !zir.Inst.Index {
// No tracy calls here, to avoid interfering with the tail call mechanism.
const map = block.sema.inst_map;
const tags = block.sema.code.instructions.items(.tag);
// TODO: As an optimization, look into making these switch prongs directly jump
// to the next one, rather than detouring through the loop condition.
// Also, look into leaving only the "noreturn" loop break condition, and removing
// the iteration based one. Better yet, have an extra entry in the tags array as a
// sentinel, so that exiting the loop is just another jump table prong.
// Related: https://github.com/ziglang/zig/issues/8220
for (body) |zir_inst| {
map[zir_inst] = switch (tags[zir_inst]) {
.alloc => try sema.zirAlloc(block, zir_inst),
.alloc_mut => try sema.zirAllocMut(block, zir_inst),
.alloc_inferred => try sema.zirAllocInferred(block, zir_inst, Type.initTag(.inferred_alloc_const)),
.alloc_inferred_mut => try sema.zirAllocInferred(block, zir_inst, Type.initTag(.inferred_alloc_mut)),
.bitcast_ref => try sema.zirBitcastRef(block, zir_inst),
.bitcast_result_ptr => try sema.zirBitcastResultPtr(block, zir_inst),
.block => try sema.zirBlock(block, zir_inst, false),
.block_comptime => try sema.zirBlock(block, zir_inst, true),
.block_flat => try sema.zirBlockFlat(block, zir_inst, false),
.block_comptime_flat => try sema.zirBlockFlat(block, zir_inst, true),
.@"break" => try sema.zirBreak(block, zir_inst),
.break_void_tok => try sema.zirBreakVoidTok(block, zir_inst),
.breakpoint => try sema.zirBreakpoint(block, zir_inst),
.call => try sema.zirCall(block, zir_inst, .auto),
.call_compile_time => try sema.zirCall(block, zir_inst, .compile_time),
.call_none => try sema.zirCallNone(block, zir_inst),
.coerce_result_ptr => try sema.zirCoerceResultPtr(block, zir_inst),
.compile_error => try sema.zirCompileError(block, zir_inst),
.compile_log => try sema.zirCompileLog(block, zir_inst),
.@"const" => try sema.zirConst(block, zir_inst),
.dbg_stmt_node => try sema.zirDbgStmtNode(block, zir_inst),
.decl_ref => try sema.zirDeclRef(block, zir_inst),
.decl_val => try sema.zirDeclVal(block, zir_inst),
// We use a while(true) loop here to avoid a redundant way of breaking out of
// the loop. The only way to break out of the loop is with a `noreturn`
// instruction.
// TODO: As an optimization, make sure the codegen for these switch prongs
// directly jump to the next one, rather than detouring through the loop
// continue expression. Related: https://github.com/ziglang/zig/issues/8220
var i: usize = 0;
while (true) : (i += 1) {
const inst = body[i];
map[inst] = switch (tags[inst]) {
.elided => continue,
.ensure_result_used => try sema.zirEnsureResultUsed(block, zir_inst),
.ensure_result_non_error => try sema.zirEnsureResultNonError(block, zir_inst),
.indexable_ptr_len => try sema.zirIndexablePtrLen(block, zir_inst),
.ref => try sema.zirRef(block, zir_inst),
.resolve_inferred_alloc => try sema.zirResolveInferredAlloc(block, zir_inst),
.ret_ptr => try sema.zirRetPtr(block, zir_inst),
.ret_type => try sema.zirRetType(block, zir_inst),
.store_to_block_ptr => try sema.zirStoreToBlockPtr(block, zir_inst),
.store_to_inferred_ptr => try sema.zirStoreToInferredPtr(block, zir_inst),
.ptr_type_simple => try sema.zirPtrTypeSimple(block, zir_inst),
.ptr_type => try sema.zirPtrType(block, zir_inst),
.store => try sema.zirStore(block, zir_inst),
.set_eval_branch_quota => try sema.zirSetEvalBranchQuota(block, zir_inst),
.str => try sema.zirStr(block, zir_inst),
.int => try sema.zirInt(block, zir_inst),
.int_type => try sema.zirIntType(block, zir_inst),
.loop => try sema.zirLoop(block, zir_inst),
.param_type => try sema.zirParamType(block, zir_inst),
.ptrtoint => try sema.zirPtrtoint(block, zir_inst),
.field_ptr => try sema.zirFieldPtr(block, zir_inst),
.field_val => try sema.zirFieldVal(block, zir_inst),
.field_ptr_named => try sema.zirFieldPtrNamed(block, zir_inst),
.field_val_named => try sema.zirFieldValNamed(block, zir_inst),
.deref_node => try sema.zirDerefNode(block, zir_inst),
.as => try sema.zirAs(block, zir_inst),
.as_node => try sema.zirAsNode(block, zir_inst),
.@"asm" => try sema.zirAsm(block, zir_inst, false),
.asm_volatile => try sema.zirAsm(block, zir_inst, true),
.@"unreachable" => try sema.zirUnreachable(block, zir_inst),
.ret_coerce => try sema.zirRetTok(block, zir_inst, true),
.ret_tok => try sema.zirRetTok(block, zir_inst, false),
.ret_node => try sema.zirRetNode(block, zir_inst),
.fn_type => try sema.zirFnType(block, zir_inst, false),
.fn_type_cc => try sema.zirFnTypeCc(block, zir_inst, false),
.fn_type_var_args => try sema.zirFnType(block, zir_inst, true),
.fn_type_cc_var_args => try sema.zirFnTypeCc(block, zir_inst, true),
.intcast => try sema.zirIntcast(block, zir_inst),
.bitcast => try sema.zirBitcast(block, zir_inst),
.floatcast => try sema.zirFloatcast(block, zir_inst),
.elem_ptr => try sema.zirElemPtr(block, zir_inst),
.elem_ptr_node => try sema.zirElemPtrNode(block, zir_inst),
.elem_val => try sema.zirElemVal(block, zir_inst),
.elem_val_node => try sema.zirElemValNode(block, zir_inst),
.add => try sema.zirArithmetic(block, zir_inst),
.addwrap => try sema.zirArithmetic(block, zir_inst),
.sub => try sema.zirArithmetic(block, zir_inst),
.subwrap => try sema.zirArithmetic(block, zir_inst),
.add => try sema.zirArithmetic(block, inst),
.addwrap => try sema.zirArithmetic(block, inst),
.alloc => try sema.zirAlloc(block, inst),
.alloc_inferred => try sema.zirAllocInferred(block, inst, Type.initTag(.inferred_alloc_const)),
.alloc_inferred_mut => try sema.zirAllocInferred(block, inst, Type.initTag(.inferred_alloc_mut)),
.alloc_mut => try sema.zirAllocMut(block, inst),
.array_cat => try sema.zirArrayCat(block, inst),
.array_mul => try sema.zirArrayMul(block, inst),
.array_type => try sema.zirArrayType(block, inst),
.array_type_sentinel => try sema.zirArrayTypeSentinel(block, inst),
.as => try sema.zirAs(block, inst),
.as_node => try sema.zirAsNode(block, inst),
.@"asm" => try sema.zirAsm(block, inst, false),
.asm_volatile => try sema.zirAsm(block, inst, true),
.bit_and => try sema.zirBitwise(block, inst),
.bit_not => try sema.zirBitNot(block, inst),
.bit_or => try sema.zirBitwise(block, inst),
.bitcast => try sema.zirBitcast(block, inst),
.bitcast_ref => try sema.zirBitcastRef(block, inst),
.bitcast_result_ptr => try sema.zirBitcastResultPtr(block, inst),
.block => try sema.zirBlock(block, inst, false),
.block_comptime => try sema.zirBlock(block, inst, true),
.bool_not => try sema.zirBoolNot(block, inst),
.bool_and => try sema.zirBoolOp(block, inst, false),
.bool_or => try sema.zirBoolOp(block, inst, true),
.bool_br_and => try sema.zirBoolBr(block, inst, false),
.bool_br_or => try sema.zirBoolBr(block, inst, true),
.call => try sema.zirCall(block, inst, .auto),
.call_compile_time => try sema.zirCall(block, inst, .compile_time),
.call_none => try sema.zirCallNone(block, inst),
.cmp_eq => try sema.zirCmp(block, inst, .eq),
.cmp_gt => try sema.zirCmp(block, inst, .gt),
.cmp_gte => try sema.zirCmp(block, inst, .gte),
.cmp_lt => try sema.zirCmp(block, inst, .lt),
.cmp_lte => try sema.zirCmp(block, inst, .lte),
.cmp_neq => try sema.zirCmp(block, inst, .neq),
.coerce_result_ptr => try sema.zirCoerceResultPtr(block, inst),
.@"const" => try sema.zirConst(block, inst),
.decl_ref => try sema.zirDeclRef(block, inst),
.decl_val => try sema.zirDeclVal(block, inst),
.deref_node => try sema.zirDerefNode(block, inst),
.div => try sema.zirArithmetic(block, inst),
.elem_ptr => try sema.zirElemPtr(block, inst),
.elem_ptr_node => try sema.zirElemPtrNode(block, inst),
.elem_val => try sema.zirElemVal(block, inst),
.elem_val_node => try sema.zirElemValNode(block, inst),
.enum_literal => try sema.zirEnumLiteral(block, inst),
.enum_literal_small => try sema.zirEnumLiteralSmall(block, inst),
.err_union_code => try sema.zirErrUnionCode(block, inst),
.err_union_code_ptr => try sema.zirErrUnionCodePtr(block, inst),
.err_union_payload_safe => try sema.zirErrUnionPayload(block, inst, true),
.err_union_payload_safe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, true),
.err_union_payload_unsafe => try sema.zirErrUnionPayload(block, inst, false),
.err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, false),
.error_set => try sema.zirErrorSet(block, inst),
.error_union_type => try sema.zirErrorUnionType(block, inst),
.error_value => try sema.zirErrorValue(block, inst),
.field_ptr => try sema.zirFieldPtr(block, inst),
.field_ptr_named => try sema.zirFieldPtrNamed(block, inst),
.field_val => try sema.zirFieldVal(block, inst),
.field_val_named => try sema.zirFieldValNamed(block, inst),
.floatcast => try sema.zirFloatcast(block, inst),
.fn_type => try sema.zirFnType(block, inst, false),
.fn_type_cc => try sema.zirFnTypeCc(block, inst, false),
.fn_type_cc_var_args => try sema.zirFnTypeCc(block, inst, true),
.fn_type_var_args => try sema.zirFnType(block, inst, true),
.import => try sema.zirImport(block, inst),
.indexable_ptr_len => try sema.zirIndexablePtrLen(block, inst),
.int => try sema.zirInt(block, inst),
.int_type => try sema.zirIntType(block, inst),
.intcast => try sema.zirIntcast(block, inst),
.is_err => try sema.zirIsErr(block, inst),
.is_err_ptr => try sema.zirIsErrPtr(block, inst),
.is_non_null => try sema.zirIsNull(block, inst, true),
.is_non_null_ptr => try sema.zirIsNullPtr(block, inst, true),
.is_null => try sema.zirIsNull(block, inst, false),
.is_null_ptr => try sema.zirIsNullPtr(block, inst, false),
.merge_error_sets => try sema.zirMergeErrorSets(block, inst),
.mod_rem => try sema.zirArithmetic(block, inst),
.mul => try sema.zirArithmetic(block, inst),
.mulwrap => try sema.zirArithmetic(block, inst),
.negate => @panic("TODO"),
.negate_wrap => @panic("TODO"),
.mul => try sema.zirArithmetic(block, zir_inst),
.mulwrap => try sema.zirArithmetic(block, zir_inst),
.div => try sema.zirArithmetic(block, zir_inst),
.mod_rem => try sema.zirArithmetic(block, zir_inst),
.array_cat => try sema.zirArrayCat(block, zir_inst),
.array_mul => try sema.zirArrayMul(block, zir_inst),
.bit_and => try sema.zirBitwise(block, zir_inst),
.bit_not => try sema.zirBitNot(block, zir_inst),
.bit_or => try sema.zirBitwise(block, zir_inst),
.xor => try sema.zirBitwise(block, zir_inst),
.shl => try sema.zirShl(block, zir_inst),
.shr => try sema.zirShr(block, zir_inst),
.cmp_lt => try sema.zirCmp(block, zir_inst, .lt),
.cmp_lte => try sema.zirCmp(block, zir_inst, .lte),
.cmp_eq => try sema.zirCmp(block, zir_inst, .eq),
.cmp_gte => try sema.zirCmp(block, zir_inst, .gte),
.cmp_gt => try sema.zirCmp(block, zir_inst, .gt),
.cmp_neq => try sema.zirCmp(block, zir_inst, .neq),
.condbr => try sema.zirCondbr(block, zir_inst),
.is_null => try sema.zirIsNull(block, zir_inst, false),
.is_non_null => try sema.zirIsNull(block, zir_inst, true),
.is_null_ptr => try sema.zirIsNullPtr(block, zir_inst, false),
.is_non_null_ptr => try sema.zirIsNullPtr(block, zir_inst, true),
.is_err => try sema.zirIsErr(block, zir_inst),
.is_err_ptr => try sema.zirIsErrPtr(block, zir_inst),
.bool_not => try sema.zirBoolNot(block, zir_inst),
.typeof => try sema.zirTypeof(block, zir_inst),
.typeof_peer => try sema.zirTypeofPeer(block, zir_inst),
.optional_type => try sema.zirOptionalType(block, zir_inst),
.optional_type_from_ptr_elem => try sema.zirOptionalTypeFromPtrElem(block, zir_inst),
.optional_payload_safe => try sema.zirOptionalPayload(block, zir_inst, true),
.optional_payload_unsafe => try sema.zirOptionalPayload(block, zir_inst, false),
.optional_payload_safe_ptr => try sema.zirOptionalPayloadPtr(block, zir_inst, true),
.optional_payload_unsafe_ptr => try sema.zirOptionalPayloadPtr(block, zir_inst, false),
.err_union_payload_safe => try sema.zirErrUnionPayload(block, zir_inst, true),
.err_union_payload_unsafe => try sema.zirErrUnionPayload(block, zir_inst, false),
.err_union_payload_safe_ptr => try sema.zirErrUnionPayloadPtr(block, zir_inst, true),
.err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, zir_inst, false),
.err_union_code => try sema.zirErrUnionCode(block, zir_inst),
.err_union_code_ptr => try sema.zirErrUnionCodePtr(block, zir_inst),
.ensure_err_payload_void => try sema.zirEnsureErrPayloadVoid(block, zir_inst),
.array_type => try sema.zirArrayType(block, zir_inst),
.array_type_sentinel => try sema.zirArrayTypeSentinel(block, zir_inst),
.enum_literal => try sema.zirEnumLiteral(block, zir_inst),
.enum_literal_small => try sema.zirEnumLiteralSmall(block, zir_inst),
.merge_error_sets => try sema.zirMergeErrorSets(block, zir_inst),
.error_union_type => try sema.zirErrorUnionType(block, zir_inst),
.error_set => try sema.zirErrorSet(block, zir_inst),
.error_value => try sema.zirErrorValue(block, zir_inst),
.slice_start => try sema.zirSliceStart(block, zir_inst),
.slice_end => try sema.zirSliceEnd(block, zir_inst),
.slice_sentinel => try sema.zirSliceSentinel(block, zir_inst),
.import => try sema.zirImport(block, zir_inst),
.bool_and => try sema.zirBoolOp(block, zir_inst, false),
.bool_or => try sema.zirBoolOp(block, zir_inst, true),
.optional_payload_safe => try sema.zirOptionalPayload(block, inst, true),
.optional_payload_safe_ptr => try sema.zirOptionalPayloadPtr(block, inst, true),
.optional_payload_unsafe => try sema.zirOptionalPayload(block, inst, false),
.optional_payload_unsafe_ptr => try sema.zirOptionalPayloadPtr(block, inst, false),
.optional_type => try sema.zirOptionalType(block, inst),
.optional_type_from_ptr_elem => try sema.zirOptionalTypeFromPtrElem(block, inst),
.param_type => try sema.zirParamType(block, inst),
.ptr_type => try sema.zirPtrType(block, inst),
.ptr_type_simple => try sema.zirPtrTypeSimple(block, inst),
.ptrtoint => try sema.zirPtrtoint(block, inst),
.ref => try sema.zirRef(block, inst),
.ret_ptr => try sema.zirRetPtr(block, inst),
.ret_type => try sema.zirRetType(block, inst),
.shl => try sema.zirShl(block, inst),
.shr => try sema.zirShr(block, inst),
.slice_end => try sema.zirSliceEnd(block, inst),
.slice_sentinel => try sema.zirSliceSentinel(block, inst),
.slice_start => try sema.zirSliceStart(block, inst),
.str => try sema.zirStr(block, inst),
.sub => try sema.zirArithmetic(block, inst),
.subwrap => try sema.zirArithmetic(block, inst),
.typeof => try sema.zirTypeof(block, inst),
.typeof_peer => try sema.zirTypeofPeer(block, inst),
.xor => try sema.zirBitwise(block, inst),
// TODO
//.switchbr => try sema.zirSwitchBr(block, zir_inst, false),
//.switchbr_ref => try sema.zirSwitchBr(block, zir_inst, true),
//.switch_range => try sema.zirSwitchRange(block, zir_inst),
//.switchbr => try sema.zirSwitchBr(block, inst, false),
//.switchbr_ref => try sema.zirSwitchBr(block, inst, true),
//.switch_range => try sema.zirSwitchRange(block, inst),
// Instructions that we know to *always* be noreturn based solely on their tag.
// These functions match the return type of analyzeBody so that we can
// tail call them here.
.condbr => return sema.zirCondbr(block, inst),
.@"break" => return sema.zirBreak(block, inst),
.break_void_tok => return sema.zirBreakVoidTok(block, inst),
.break_flat => return sema.code.instructions.items(.data)[inst].un_node.operand,
.compile_error => return sema.zirCompileError(block, inst),
.ret_coerce => return sema.zirRetTok(block, inst, true),
.ret_node => return sema.zirRetNode(block, inst),
.ret_tok => return sema.zirRetTok(block, inst, false),
.@"unreachable" => return sema.zirUnreachable(block, inst),
.loop => return sema.zirLoop(block, inst),
// Instructions that we know can *never* be noreturn based solely on
// their tag. We avoid needlessly checking if they are noreturn and
// continue the loop.
// We also know that they cannot be referenced later, so we avoid
// putting them into the map.
.breakpoint => {
try sema.zirBreakpoint(block, inst);
continue;
},
.dbg_stmt_node => {
try sema.zirDbgStmtNode(block, inst);
continue;
},
.ensure_err_payload_void => {
try sema.zirEnsureErrPayloadVoid(block, inst);
continue;
},
.ensure_result_non_error => {
try sema.zirEnsureResultNonError(block, inst);
continue;
},
.ensure_result_used => {
try sema.zirEnsureResultUsed(block, inst);
continue;
},
.compile_log => {
try sema.zirCompileLog(block, inst);
continue;
},
.set_eval_branch_quota => {
try sema.zirSetEvalBranchQuota(block, inst);
continue;
},
.store => {
try sema.zirStore(block, inst);
continue;
},
.store_to_block_ptr => {
try sema.zirStoreToBlockPtr(block, inst);
continue;
},
.store_to_inferred_ptr => {
try sema.zirStoreToInferredPtr(block, inst);
continue;
},
.resolve_inferred_alloc => {
try sema.zirResolveInferredAlloc(block, inst);
continue;
},
};
if (map[zir_inst].ty.isNoReturn()) {
break;
}
if (map[inst].ty.isNoReturn())
return always_noreturn;
}
}
@ -392,7 +450,7 @@ fn zirRetType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError
return sema.mod.constType(sema.arena, src, ret_type);
}
fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
@ -400,12 +458,12 @@ fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) I
const operand = try sema.resolveInst(inst_data.operand);
const src = inst_data.src();
switch (operand.ty.zigTypeTag()) {
.Void, .NoReturn => return sema.mod.constVoid(sema.arena, .unneeded),
.Void, .NoReturn => return,
else => return sema.mod.fail(&block.base, src, "expression value is ignored", .{}),
}
}
fn zirEnsureResultNonError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirEnsureResultNonError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
@ -414,7 +472,7 @@ fn zirEnsureResultNonError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Inde
const src = inst_data.src();
switch (operand.ty.zigTypeTag()) {
.ErrorSet, .ErrorUnion => return sema.mod.fail(&block.base, src, "error is discarded", .{}),
else => return sema.mod.constVoid(sema.arena, .unneeded),
else => return,
}
}
@ -508,11 +566,7 @@ fn zirAllocInferred(
return result;
}
fn zirResolveInferredAlloc(
sema: *Sema,
block: *Scope.Block,
inst: zir.Inst.Index,
) InnerError!*Inst {
fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
@ -536,15 +590,9 @@ fn zirResolveInferredAlloc(
// Change it to a normal alloc.
ptr.ty = final_ptr_ty;
ptr.tag = .alloc;
return sema.mod.constVoid(sema.arena, .unneeded);
}
fn zirStoreToBlockPtr(
sema: *Sema,
block: *Scope.Block,
inst: zir.Inst.Index,
) InnerError!*Inst {
fn zirStoreToBlockPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
@ -560,11 +608,7 @@ fn zirStoreToBlockPtr(
return sema.storePtr(block, src, bitcasted_ptr, value);
}
fn zirStoreToInferredPtr(
sema: *Sema,
block: *Scope.Block,
inst: zir.Inst.Index,
) InnerError!*Inst {
fn zirStoreToInferredPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
@ -583,21 +627,16 @@ fn zirStoreToInferredPtr(
return sema.storePtr(block, src, bitcasted_ptr, value);
}
fn zirSetEvalBranchQuota(
sema: *Sema,
block: *Scope.Block,
inst: zir.Inst.Index,
) InnerError!*Inst {
fn zirSetEvalBranchQuota(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
const src = inst_data.src();
try sema.requireFunctionBlock(block, src);
const quota = try sema.resolveAlreadyCoercedInt(block, src, inst_data.operand, u32);
if (sema.branch_quota < quota)
sema.branch_quota = quota;
return sema.mod.constVoid(sema.arena, .unneeded);
}
fn zirStore(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirStore(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
@ -677,7 +716,7 @@ fn zirInt(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*In
return sema.mod.constIntUnsigned(sema.arena, .unneeded, Type.initTag(.comptime_int), int);
}
fn zirCompileError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirCompileError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
const tracy = trace(@src());
defer tracy.end();
@ -688,7 +727,7 @@ fn zirCompileError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) Inner
return sema.mod.fail(&block.base, src, "{s}", .{msg});
}
fn zirCompileLog(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirCompileLog(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
var managed = sema.mod.compile_log_text.toManaged(sema.gpa);
defer sema.mod.compile_log_text = managed.moveToUnmanaged();
const writer = managed.writer();
@ -711,10 +750,9 @@ fn zirCompileLog(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerEr
if (!gop.found_existing) {
gop.entry.value = inst_data.src().toSrcLoc(&block.base);
}
return sema.mod.constVoid(sema.arena, .unneeded);
}
fn zirLoop(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirLoop(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@ -746,41 +784,13 @@ fn zirLoop(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) InnerE
};
defer child_block.instructions.deinit(sema.gpa);
try sema.analyzeBody(&child_block, body);
_ = try sema.analyzeBody(&child_block, body);
// Loop repetition is implied so the last instruction may or may not be a noreturn instruction.
try parent_block.instructions.append(sema.gpa, &loop_inst.base);
loop_inst.body = .{ .instructions = try sema.arena.dupe(*Inst, child_block.instructions.items) };
return &loop_inst.base;
}
fn zirBlockFlat(
sema: *Sema,
parent_block: *Scope.Block,
inst: zir.Inst.Index,
is_comptime: bool,
) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
const extra = sema.code.extraData(zir.Inst.MultiOp, inst_data.payload_index);
const body = sema.code.extra[extra.end..][0..extra.data.operands_len];
var child_block = parent_block.makeSubBlock();
defer child_block.instructions.deinit(sema.gpa);
child_block.is_comptime = child_block.is_comptime or is_comptime;
try sema.analyzeBody(&child_block, body);
// Move the analyzed instructions into the parent block arena.
const copied_instructions = try sema.arena.dupe(*Inst, child_block.instructions.items);
try parent_block.instructions.appendSlice(sema.gpa, copied_instructions);
// The result of a flat block is the last instruction.
return sema.inst_map[body[body.len - 1]];
return always_noreturn;
}
fn zirBlock(
@ -794,8 +804,8 @@ fn zirBlock(
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
const extra = sema.code.extraData(zir.Inst.MultiOp, inst_data.payload_index);
const body = sema.code.extra[extra.end..][0..extra.data.operands_len];
const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
const body = sema.code.extra[extra.end..][0..extra.data.body_len];
// Reserve space for a Block instruction so that generated Break instructions can
// point to it, even if it doesn't end up getting used because the code ends up being
@ -833,7 +843,7 @@ fn zirBlock(
defer merges.results.deinit(sema.gpa);
defer merges.br_list.deinit(sema.gpa);
try sema.analyzeBody(&child_block, body);
_ = try sema.analyzeBody(&child_block, body);
return sema.analyzeBlockBody(parent_block, &child_block, merges);
}
@ -919,17 +929,17 @@ fn analyzeBlockBody(
return &merges.block_inst.base;
}
fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
const src_node = sema.code.instructions.items(.data)[inst].node;
const src: LazySrcLoc = .{ .node_offset = src_node };
try sema.requireRuntimeBlock(block, src);
return block.addNoOp(src, Type.initTag(.void), .breakpoint);
_ = try block.addNoOp(src, Type.initTag(.void), .breakpoint);
}
fn zirBreak(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirBreak(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
const tracy = trace(@src());
defer tracy.end();
@ -939,7 +949,7 @@ fn zirBreak(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*
return sema.analyzeBreak(block, sema.src, zir_block, operand);
}
fn zirBreakVoidTok(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirBreakVoidTok(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
const tracy = trace(@src());
defer tracy.end();
@ -955,7 +965,7 @@ fn analyzeBreak(
src: LazySrcLoc,
zir_block: zir.Inst.Index,
operand: *Inst,
) InnerError!*Inst {
) InnerError!zir.Inst.Ref {
var block = start_block;
while (true) {
if (block.label) |*label| {
@ -981,26 +991,24 @@ fn analyzeBreak(
try block.instructions.append(sema.gpa, &br.base);
try label.merges.results.append(sema.gpa, operand);
try label.merges.br_list.append(sema.gpa, br);
return &br.base;
return always_noreturn;
}
}
block = block.parent.?;
}
}
fn zirDbgStmtNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirDbgStmtNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
if (block.is_comptime) {
return sema.mod.constVoid(sema.arena, .unneeded);
}
if (block.is_comptime) return;
const src_node = sema.code.instructions.items(.data)[inst].node;
const src: LazySrcLoc = .{ .node_offset = src_node };
const src_loc = src.toSrcLoc(&block.base);
const abs_byte_off = try src_loc.byteOffset();
return block.addDbgStmt(src, abs_byte_off);
_ = try block.addDbgStmt(src, abs_byte_off);
}
fn zirDeclRef(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
@ -1185,7 +1193,7 @@ fn analyzeCall(
// This will have return instructions analyzed as break instructions to
// the block_inst above.
try sema.root(&child_block);
_ = try sema.root(&child_block);
return sema.analyzeBlockBody(block, &child_block, merges);
}
@ -1638,7 +1646,7 @@ fn zirErrUnionCodePtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) In
return block.addUnOp(src, operand.ty.castTag(.error_union).?.data.payload, .unwrap_errunion_err_ptr, operand);
}
fn zirEnsureErrPayloadVoid(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirEnsureErrPayloadVoid(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
@ -1650,7 +1658,6 @@ fn zirEnsureErrPayloadVoid(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Inde
if (operand.ty.castTag(.error_union).?.data.payload.zigTypeTag() != .Void) {
return sema.mod.fail(&block.base, src, "expression value is ignored", .{});
}
return sema.mod.constVoid(sema.arena, .unneeded);
}
fn zirFnType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index, var_args: bool) InnerError!*Inst {
@ -2067,7 +2074,7 @@ fn zirSwitchBr(
parent_block: *Scope.Block,
inst: zir.Inst.Index,
ref: bool,
) InnerError!*Inst {
) InnerError!zir.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@ -2087,18 +2094,18 @@ fn zirSwitchBr(
const item = try sema.resolveConstValue(parent_block, case_src, casted);
if (target_val.eql(item)) {
try sema.analyzeBody(parent_block, case.body);
return sema.mod.constNoReturn(sema.arena, inst.base.src);
_ = try sema.analyzeBody(parent_block, case.body);
return always_noreturn;
}
}
try sema.analyzeBody(parent_block, inst.positionals.else_body);
return sema.mod.constNoReturn(sema.arena, inst.base.src);
_ = try sema.analyzeBody(parent_block, inst.positionals.else_body);
return always_noreturn;
}
if (inst.positionals.cases.len == 0) {
// no cases just analyze else_branch
try sema.analyzeBody(parent_block, inst.positionals.else_body);
return sema.mod.constNoReturn(sema.arena, inst.base.src);
_ = try sema.analyzeBody(parent_block, inst.positionals.else_body);
return always_noreturn;
}
try sema.requireRuntimeBlock(parent_block, inst.base.src);
@ -2122,7 +2129,7 @@ fn zirSwitchBr(
const casted = try sema.coerce(block, target.ty, resolved, resolved_src);
const item = try sema.resolveConstValue(parent_block, case_src, casted);
try sema.analyzeBody(&case_block, case.body);
_ = try sema.analyzeBody(&case_block, case.body);
cases[i] = .{
.item = item,
@ -2131,7 +2138,7 @@ fn zirSwitchBr(
}
case_block.instructions.items.len = 0;
try sema.analyzeBody(&case_block, inst.positionals.else_body);
_ = try sema.analyzeBody(&case_block, inst.positionals.else_body);
const else_body: ir.Body = .{
.instructions = try sema.arena.dupe(*Inst, case_block.instructions.items),
@ -2719,6 +2726,75 @@ fn zirBoolOp(
return block.addBinOp(src, bool_type, tag, lhs, rhs);
}
fn zirBoolBr(
sema: *Sema,
parent_block: *Scope.Block,
inst: zir.Inst.Index,
is_bool_or: bool,
) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[inst].bool_br;
const src: LazySrcLoc = .unneeded;
const lhs = try sema.resolveInst(inst_data.lhs);
const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
const body = sema.code.extra[extra.end..][0..extra.data.body_len];
if (try sema.resolveDefinedValue(parent_block, src, lhs)) |lhs_val| {
if (lhs_val.toBool() == is_bool_or) {
return sema.mod.constBool(sema.arena, src, is_bool_or);
}
// comptime-known left-hand side. No need for a block here; the result
// is simply the rhs expression. Here we rely on there only being 1
// break instruction (`break_flat`).
const zir_inst_ref = try sema.analyzeBody(parent_block, body);
return sema.resolveInst(zir_inst_ref);
}
const block_inst = try sema.arena.create(Inst.Block);
block_inst.* = .{
.base = .{
.tag = Inst.Block.base_tag,
.ty = Type.initTag(.bool),
.src = src,
},
.body = undefined,
};
var child_block = parent_block.makeSubBlock();
defer child_block.instructions.deinit(sema.gpa);
var then_block = child_block.makeSubBlock();
defer then_block.instructions.deinit(sema.gpa);
var else_block = child_block.makeSubBlock();
defer else_block.instructions.deinit(sema.gpa);
const lhs_block = if (is_bool_or) &then_block else &else_block;
const rhs_block = if (is_bool_or) &else_block else &then_block;
const lhs_result = try sema.mod.constInst(sema.arena, src, .{
.ty = Type.initTag(.bool),
.val = if (is_bool_or) Value.initTag(.bool_true) else Value.initTag(.bool_false),
});
_ = try lhs_block.addBr(src, block_inst, lhs_result);
const rhs_result_zir_ref = try sema.analyzeBody(rhs_block, body);
const rhs_result = try sema.resolveInst(rhs_result_zir_ref);
_ = try rhs_block.addBr(src, block_inst, rhs_result);
const tzir_then_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, then_block.instructions.items) };
const tzir_else_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, rhs_block.instructions.items) };
_ = try child_block.addCondBr(src, lhs, tzir_then_body, tzir_else_body);
block_inst.body = .{
.instructions = try sema.arena.dupe(*Inst, child_block.instructions.items),
};
try parent_block.instructions.append(sema.gpa, &block_inst.base);
return &block_inst.base;
}
fn zirIsNull(
sema: *Sema,
block: *Scope.Block,
@ -2770,7 +2846,11 @@ fn zirIsErrPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerErro
return sema.analyzeIsErr(block, src, loaded);
}
fn zirCondbr(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirCondbr(
sema: *Sema,
parent_block: *Scope.Block,
inst: zir.Inst.Index,
) InnerError!zir.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@ -2787,8 +2867,8 @@ fn zirCondbr(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) Inne
if (try sema.resolveDefinedValue(parent_block, src, cond)) |cond_val| {
const body = if (cond_val.toBool()) then_body else else_body;
try sema.analyzeBody(parent_block, body);
return sema.mod.constNoReturn(sema.arena, src);
_ = try sema.analyzeBody(parent_block, body);
return always_noreturn;
}
var true_block: Scope.Block = .{
@ -2800,7 +2880,7 @@ fn zirCondbr(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) Inne
.is_comptime = parent_block.is_comptime,
};
defer true_block.instructions.deinit(sema.gpa);
try sema.analyzeBody(&true_block, then_body);
_ = try sema.analyzeBody(&true_block, then_body);
var false_block: Scope.Block = .{
.parent = parent_block,
@ -2811,14 +2891,15 @@ fn zirCondbr(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) Inne
.is_comptime = parent_block.is_comptime,
};
defer false_block.instructions.deinit(sema.gpa);
try sema.analyzeBody(&false_block, else_body);
_ = try sema.analyzeBody(&false_block, else_body);
const tzir_then_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, true_block.instructions.items) };
const tzir_else_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, false_block.instructions.items) };
return parent_block.addCondBr(src, cond, tzir_then_body, tzir_else_body);
_ = try parent_block.addCondBr(src, cond, tzir_then_body, tzir_else_body);
return always_noreturn;
}
fn zirUnreachable(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirUnreachable(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@ -2830,7 +2911,8 @@ fn zirUnreachable(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerE
if (safety_check and block.wantSafety()) {
return sema.safetyPanic(block, src, .unreach);
} else {
return block.addNoOp(src, Type.initTag(.noreturn), .unreach);
_ = try block.addNoOp(src, Type.initTag(.noreturn), .unreach);
return always_noreturn;
}
}
@ -2839,7 +2921,7 @@ fn zirRetTok(
block: *Scope.Block,
inst: zir.Inst.Index,
need_coercion: bool,
) InnerError!*Inst {
) InnerError!zir.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@ -2850,7 +2932,7 @@ fn zirRetTok(
return sema.analyzeRet(block, operand, src, need_coercion);
}
fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@ -2867,22 +2949,24 @@ fn analyzeRet(
operand: *Inst,
src: LazySrcLoc,
need_coercion: bool,
) InnerError!*Inst {
) InnerError!zir.Inst.Ref {
if (block.inlining) |inlining| {
// We are inlining a function call; rewrite the `ret` as a `break`.
try inlining.merges.results.append(sema.gpa, operand);
const br = try block.addBr(src, inlining.merges.block_inst, operand);
return &br.base;
_ = try block.addBr(src, inlining.merges.block_inst, operand);
return always_noreturn;
}
if (need_coercion) {
if (sema.func) |func| {
const fn_ty = func.owner_decl.typed_value.most_recent.typed_value.ty;
const casted_operand = try sema.coerce(block, fn_ty.fnReturnType(), operand, src);
return block.addUnOp(src, Type.initTag(.noreturn), .ret, casted_operand);
_ = try block.addUnOp(src, Type.initTag(.noreturn), .ret, casted_operand);
return always_noreturn;
}
}
return block.addUnOp(src, Type.initTag(.noreturn), .ret, operand);
_ = try block.addUnOp(src, Type.initTag(.noreturn), .ret, operand);
return always_noreturn;
}
fn floatOpAllowed(tag: zir.Inst.Tag) bool {
@ -3051,10 +3135,11 @@ fn addSafetyCheck(sema: *Sema, parent_block: *Scope.Block, ok: *Inst, panic_id:
try parent_block.instructions.append(sema.gpa, &block_inst.base);
}
fn safetyPanic(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, panic_id: PanicId) !*Inst {
fn safetyPanic(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, panic_id: PanicId) !zir.Inst.Ref {
// TODO Once we have a panic function to call, call it here instead of breakpoint.
_ = try block.addNoOp(src, Type.initTag(.void), .breakpoint);
return block.addNoOp(src, Type.initTag(.noreturn), .unreach);
_ = try block.addNoOp(src, Type.initTag(.noreturn), .unreach);
return always_noreturn;
}
fn emitBackwardBranch(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void {
@ -3405,20 +3490,20 @@ fn storePtr(
src: LazySrcLoc,
ptr: *Inst,
uncasted_value: *Inst,
) !*Inst {
) !void {
if (ptr.ty.isConstPtr())
return sema.mod.fail(&block.base, src, "cannot assign to constant", .{});
const elem_ty = ptr.ty.elemType();
const value = try sema.coerce(block, elem_ty, uncasted_value, uncasted_value.src);
if (elem_ty.onePossibleValue() != null)
return sema.mod.constVoid(sema.arena, .unneeded);
return;
// TODO handle comptime pointer writes
// TODO handle if the element type requires comptime
try sema.requireRuntimeBlock(block, src);
return block.addBinOp(src, Type.initTag(.void), .store, ptr, value);
_ = try block.addBinOp(src, Type.initTag(.void), .store, ptr, value);
}
fn bitcast(sema: *Sema, block: *Scope.Block, dest_type: Type, inst: *Inst) !*Inst {

View File

@ -370,8 +370,8 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) In
.array_cat => return simpleBinOp(mod, scope, rl, node, .array_cat),
.array_mult => return simpleBinOp(mod, scope, rl, node, .array_mul),
.bool_and => return boolBinOp(mod, scope, rl, node, .bool_and),
.bool_or => return boolBinOp(mod, scope, rl, node, .bool_or),
.bool_and => return boolBinOp(mod, scope, rl, node, .bool_br_and),
.bool_or => return boolBinOp(mod, scope, rl, node, .bool_br_or),
.bool_not => return boolNot(mod, scope, rl, node),
.bit_not => return bitNot(mod, scope, rl, node),
@ -425,8 +425,8 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) In
.field_access => return fieldAccess(mod, scope, rl, node),
.float_literal => return floatLiteral(mod, scope, rl, node),
.if_simple => return ifExpr(mod, scope, rl, tree.ifSimple(node)),
.@"if" => return ifExpr(mod, scope, rl, tree.ifFull(node)),
.if_simple => return ifExpr(mod, scope, rl, node, tree.ifSimple(node)),
.@"if" => return ifExpr(mod, scope, rl, node, tree.ifFull(node)),
.while_simple => return whileExpr(mod, scope, rl, tree.whileSimple(node)),
.while_cont => return whileExpr(mod, scope, rl, tree.whileCont(node)),
@ -923,6 +923,7 @@ fn labeledBlockExpr(
// so that break statements can reference it.
const gz = parent_scope.getGenZir();
const block_inst = try gz.addBlock(zir_tag, block_node);
try gz.instructions.append(mod.gpa, block_inst);
var block_scope: Scope.GenZir = .{
.parent = parent_scope,
@ -946,8 +947,6 @@ fn labeledBlockExpr(
return mod.failTok(parent_scope, label_token, "unused block label", .{});
}
try gz.instructions.append(mod.gpa, block_inst);
const zir_tags = gz.zir_code.instructions.items(.tag);
const zir_datas = gz.zir_code.instructions.items(.data);
@ -961,7 +960,7 @@ fn labeledBlockExpr(
}
// TODO technically not needed since we changed the tag to break_void but
// would be better still to elide the ones that are in this list.
try copyBodyNoEliding(block_inst, block_scope);
try block_scope.setBlockBody(block_inst);
return gz.zir_code.ref_start_index + block_inst;
},
@ -975,7 +974,7 @@ fn labeledBlockExpr(
// TODO technically not needed since we changed the tag to elided but
// would be better still to elide the ones that are in this list.
}
try copyBodyNoEliding(block_inst, block_scope);
try block_scope.setBlockBody(block_inst);
const block_ref = gz.zir_code.ref_start_index + block_inst;
switch (rl) {
.ref => return block_ref,
@ -1635,8 +1634,8 @@ fn finishThenElseBlock(
});
}
assert(!strat.elide_store_to_block_ptr_instructions);
try copyBodyNoEliding(then_body, then_scope.*);
try copyBodyNoEliding(else_body, else_scope.*);
try then_scope.setBlockBody(then_body);
try else_scope.setBlockBody(else_body);
return &main_block.base;
},
.break_operand => {
@ -1662,8 +1661,8 @@ fn finishThenElseBlock(
try copyBodyWithElidedStoreBlockPtr(then_body, then_scope.*);
try copyBodyWithElidedStoreBlockPtr(else_body, else_scope.*);
} else {
try copyBodyNoEliding(then_body, then_scope.*);
try copyBodyNoEliding(else_body, else_scope.*);
try then_scope.setBlockBody(then_body);
try else_scope.setBlockBody(else_body);
}
switch (rl) {
.ref => return &main_block.base,
@ -1801,95 +1800,49 @@ fn boolBinOp(
mod: *Module,
scope: *Scope,
rl: ResultLoc,
infix_node: ast.Node.Index,
kind: enum { bool_and, bool_or },
node: ast.Node.Index,
zir_tag: zir.Inst.Tag,
) InnerError!zir.Inst.Ref {
const tree = scope.tree();
const node_datas = tree.nodes.items(.data);
const bool_type = @enumToInt(zir.Const.bool_type);
const gz = scope.getGenZir();
const node_datas = gz.tree().nodes.items(.data);
const bool_type = @enumToInt(zir.Const.bool_type);
const lhs = try expr(mod, scope, .{ .ty = bool_type }, node_datas[infix_node].lhs);
const lhs = try expr(mod, scope, .{ .ty = bool_type }, node_datas[node].lhs);
const bool_br = try gz.addBoolBr(zir_tag, lhs);
const block_inst = try gz.addBlock(.block, infix_node);
const block_ref = gz.zir_code.ref_start_index + block_inst;
var block_scope: Scope.GenZir = .{
var rhs_scope: Scope.GenZir = .{
.parent = scope,
.zir_code = gz.zir_code,
.force_comptime = gz.force_comptime,
};
defer block_scope.instructions.deinit(mod.gpa);
var rhs_scope: Scope.GenZir = .{
.parent = &block_scope.base,
.zir_code = gz.zir_code,
.force_comptime = gz.force_comptime,
};
defer rhs_scope.instructions.deinit(mod.gpa);
const rhs = try expr(mod, &rhs_scope.base, .{ .ty = bool_type }, node_datas[infix_node].rhs);
_ = try rhs_scope.addBin(.@"break", block_inst, rhs);
const rhs = try expr(mod, &rhs_scope.base, .{ .ty = bool_type }, node_datas[node].rhs);
_ = try rhs_scope.addUnNode(.break_flat, rhs, node);
try rhs_scope.setBoolBrBody(bool_br);
// TODO: should we have zir.Const instructions for `break true` and `break false`?
const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len);
const break_true_false_ref = new_index + gz.zir_code.ref_start_index;
try gz.zir_code.instructions.append(gz.zir_code.gpa, .{ .tag = .@"break", .data = .{ .bin = .{
.lhs = block_inst,
.rhs = switch (kind) {
.bool_and => @enumToInt(zir.Const.bool_false),
.bool_or => @enumToInt(zir.Const.bool_true),
},
} } });
switch (kind) {
// if lhs // AND
// break rhs
// else
// break false
.bool_and => _ = try block_scope.addCondBr(
lhs,
rhs_scope.instructions.items,
&[_]zir.Inst.Ref{break_true_false_ref},
infix_node,
),
// if lhs // OR
// break true
// else
// break rhs
.bool_or => _ = try block_scope.addCondBr(
lhs,
&[_]zir.Inst.Ref{break_true_false_ref},
rhs_scope.instructions.items,
infix_node,
),
}
try gz.instructions.append(mod.gpa, block_inst);
try copyBodyNoEliding(block_inst, block_scope);
return rvalue(mod, scope, rl, block_ref, infix_node);
const block_ref = gz.zir_code.ref_start_index + bool_br;
return rvalue(mod, scope, rl, block_ref, node);
}
fn ifExpr(
mod: *Module,
scope: *Scope,
rl: ResultLoc,
node: ast.Node.Index,
if_full: ast.full.If,
) InnerError!zir.Inst.Ref {
if (true) @panic("TODO update for zir-memory-layout");
const parent_gz = scope.getGenZir();
var block_scope: Scope.GenZir = .{
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
.zir_code = parent_gz.zir_code,
.force_comptime = scope.isComptime(),
.instructions = .{},
};
setBlockResultLoc(&block_scope, rl);
defer block_scope.instructions.deinit(mod.gpa);
const tree = scope.tree();
const main_tokens = tree.nodes.items(.main_token);
const if_src = token_starts[if_full.ast.if_token];
const tree = parent_gz.tree();
const cond = c: {
// TODO https://github.com/ziglang/zig/issues/7929
@ -1898,23 +1851,16 @@ fn ifExpr(
} else if (if_full.payload_token) |payload_token| {
return mod.failTok(scope, payload_token, "TODO implement if optional", .{});
} else {
const bool_type = try addZIRInstConst(mod, &block_scope.base, if_src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.bool_type),
});
break :c try expr(mod, &block_scope.base, .{ .ty = bool_type }, if_full.ast.cond_expr);
const bool_rl: ResultLoc = .{ .ty = @enumToInt(zir.Const.bool_type) };
break :c try expr(mod, &block_scope.base, bool_rl, if_full.ast.cond_expr);
}
};
const condbr = try addZIRInstSpecial(mod, &block_scope.base, if_src, zir.Inst.CondBr, .{
.condition = cond,
.then_body = undefined, // populated below
.else_body = undefined, // populated below
}, .{});
const condbr = try block_scope.addCondBr(node);
const block = try addZIRInstBlock(mod, scope, if_src, .block, .{
.instructions = try block_scope.arena.dupe(zir.Inst.Ref, block_scope.instructions.items),
});
const block = try parent_gz.addBlock(.block, node);
try parent_gz.instructions.append(mod.gpa, block);
try block_scope.setBlockBody(block);
const then_src = token_starts[tree.lastToken(if_full.ast.then_expr)];
var then_scope: Scope.GenZir = .{
@ -1990,12 +1936,6 @@ fn copyBodyWithElidedStoreBlockPtr(body: *zir.Body, scope: Module.Scope.GenZir)
assert(dst_index == body.instructions.len);
}
fn copyBodyNoEliding(block_inst: zir.Inst.Index, gz: Module.Scope.GenZir) !void {
const zir_datas = gz.zir_code.instructions.items(.data);
zir_datas[block_inst].pl_node.payload_index = @intCast(u32, gz.zir_code.extra.items.len);
try gz.zir_code.extra.appendSlice(gz.zir_code.gpa, gz.instructions.items);
}
fn whileExpr(
mod: *Module,
scope: *Scope,

View File

@ -99,11 +99,7 @@ pub const Code = struct {
try stderr.print("ZIR {s} {s} {{\n", .{ kind, decl_name });
const root_body = code.extra[code.root_start..][0..code.root_len];
for (root_body) |inst| {
try stderr.print(" %{d} ", .{inst});
try writer.writeInstToStream(stderr, inst);
try stderr.writeByte('\n');
}
try writer.writeBody(stderr, root_body);
try stderr.print("}} // ZIR {s} {s}\n\n", .{ kind, decl_name });
}
@ -451,16 +447,10 @@ pub const Inst = struct {
/// Bitwise OR. `|`
bit_or,
/// A labeled block of code, which can return a value.
/// Uses the `pl_node` union field. Payload is `MultiOp`.
/// Uses the `pl_node` union field. Payload is `Block`.
block,
/// A block of code, which can return a value. There are no instructions that break out of
/// this block; it is implied that the final instruction is the result.
/// Uses the `pl_node` union field. Payload is `MultiOp`.
block_flat,
/// Same as `block` but additionally makes the inner instructions execute at comptime.
block_comptime,
/// Same as `block_flat` but additionally makes the inner instructions execute at comptime.
block_comptime_flat,
/// Boolean AND. See also `bit_and`.
/// Uses the `pl_node` union field. Payload is `Bin`.
bool_and,
@ -470,6 +460,14 @@ pub const Inst = struct {
/// Boolean OR. See also `bit_or`.
/// Uses the `pl_node` union field. Payload is `Bin`.
bool_or,
/// Short-circuiting boolean `and`. `lhs` is a boolean `Ref` and the other operand
/// is a block, which is evaluated if `lhs` is `true`.
/// Uses the `bool_br` union field.
bool_br_and,
/// Short-circuiting boolean `or`. `lhs` is a boolean `Ref` and the other operand
/// is a block, which is evaluated if `lhs` is `false`.
/// Uses the `bool_br` union field.
bool_br_or,
/// Return a value from a block.
/// Uses the `bin` union field: `lhs` is `Index` to the block (*not* `Ref`!),
/// `rhs` is operand.
@ -480,6 +478,12 @@ pub const Inst = struct {
/// Uses the `un_tok` union field.
/// Note that the block operand is a `Index`, not `Ref`.
break_void_tok,
/// Return a value from a block. This is a special form that is only valid
/// when there is exactly 1 break from a block (this one). This instruction
/// allows using the return value from `Sema.analyzeBody`. The block is
/// assumed to be the direct parent of this instruction.
/// Uses the `un_node` union field. The AST node is unused.
break_flat,
/// Uses the `node` union field.
breakpoint,
/// Function call with modifier `.auto`.
@ -637,7 +641,7 @@ pub const Inst = struct {
/// A labeled block of code that loops forever. At the end of the body it is implied
/// to repeat; no explicit "repeat" instruction terminates loop bodies.
/// Uses the `pl_node` field. The AST node is either a for loop or while loop.
/// The payload is `MultiOp`.
/// The payload is `Block`.
loop,
/// Merge two error sets into one, `E1 || E2`.
merge_error_sets,
@ -886,9 +890,9 @@ pub const Inst = struct {
.bitcast_result_ptr,
.bit_or,
.block,
.block_flat,
.block_comptime,
.block_comptime_flat,
.bool_br_and,
.bool_br_or,
.bool_not,
.bool_and,
.bool_or,
@ -988,6 +992,7 @@ pub const Inst = struct {
.@"break",
.break_void_tok,
.break_flat,
.condbr,
.compile_error,
.ret_node,
@ -1127,6 +1132,11 @@ pub const Inst = struct {
/// For `fn_type_cc` this points to `FnTypeCc` in `extra`.
payload_index: u32,
},
bool_br: struct {
lhs: Ref,
/// Points to a `Block`.
payload_index: u32,
},
param_type: struct {
callee: Ref,
param_index: u32,
@ -1191,6 +1201,12 @@ pub const Inst = struct {
operands_len: u32,
};
/// This data is stored inside extra, with trailing operands according to `body_len`.
/// Each operand is an `Index`.
pub const Block = struct {
body_len: u32,
};
/// Stored inside extra, with trailing arguments according to `args_len`.
/// Each argument is a `Ref`.
pub const Call = struct {
@ -1342,6 +1358,7 @@ const Writer = struct {
.err_union_payload_unsafe_ptr,
.err_union_code,
.err_union_code_ptr,
.break_flat,
=> try self.writeUnNode(stream, inst),
.break_void_tok,
@ -1358,6 +1375,10 @@ const Writer = struct {
.ensure_err_payload_void,
=> try self.writeUnTok(stream, inst),
.bool_br_and,
.bool_br_or,
=> try self.writeBoolBr(stream, inst),
.array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst),
.@"const" => try self.writeConst(stream, inst),
.param_type => try self.writeParamType(stream, inst),
@ -1370,9 +1391,7 @@ const Writer = struct {
.@"asm",
.asm_volatile,
.block,
.block_flat,
.block_comptime,
.block_comptime_flat,
.call,
.call_compile_time,
.compile_log,
@ -1618,6 +1637,19 @@ const Writer = struct {
return self.writeFnTypeCommon(stream, param_types, inst_data.return_type, var_args, cc);
}
fn writeBoolBr(self: *Writer, stream: anytype, inst: Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].bool_br;
const extra = self.code.extraData(Inst.Block, inst_data.payload_index);
const body = self.code.extra[extra.end..][0..extra.data.body_len];
try self.writeInstRef(stream, inst_data.lhs);
try stream.writeAll(", {\n");
self.indent += 2;
try self.writeBody(stream, body);
self.indent -= 2;
try stream.writeByteNTimes(' ', self.indent);
try stream.writeAll("})");
}
fn writeFnTypeCc(
self: *Writer,
stream: anytype,
@ -1713,4 +1745,13 @@ const Writer = struct {
@tagName(src), delta_line.line + 1, delta_line.column + 1,
});
}
fn writeBody(self: *Writer, stream: anytype, body: []const Inst.Index) !void {
for (body) |inst| {
try stream.writeByteNTimes(' ', self.indent);
try stream.print("%{d} ", .{inst});
try self.writeInstToStream(stream, inst);
try stream.writeByte('\n');
}
}
};