diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 029e7d4b8c..1827d53043 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -48,20 +48,21 @@ pub fn typeExpr(mod: *Module, scope: *Scope, type_node: *ast.Node) InnerError!*z pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerError!*zir.Inst { switch (node.tag) { .VarDecl => unreachable, // Handled in `blockExpr`. - .Assign => unreachable, // Handled in `blockExpr`. - .AssignBitAnd => unreachable, // Handled in `blockExpr`. - .AssignBitOr => unreachable, // Handled in `blockExpr`. - .AssignBitShiftLeft => unreachable, // Handled in `blockExpr`. - .AssignBitShiftRight => unreachable, // Handled in `blockExpr`. - .AssignBitXor => unreachable, // Handled in `blockExpr`. - .AssignDiv => unreachable, // Handled in `blockExpr`. - .AssignSub => unreachable, // Handled in `blockExpr`. - .AssignSubWrap => unreachable, // Handled in `blockExpr`. - .AssignMod => unreachable, // Handled in `blockExpr`. - .AssignAdd => unreachable, // Handled in `blockExpr`. - .AssignAddWrap => unreachable, // Handled in `blockExpr`. - .AssignMul => unreachable, // Handled in `blockExpr`. - .AssignMulWrap => unreachable, // Handled in `blockExpr`. + + .Assign => return rlWrapVoid(mod, scope, rl, node, try assign(mod, scope, node.castTag(.Assign).?)), + .AssignBitAnd => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignBitAnd).?, .bitand)), + .AssignBitOr => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignBitOr).?, .bitor)), + .AssignBitShiftLeft => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignBitShiftLeft).?, .shl)), + .AssignBitShiftRight => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignBitShiftRight).?, .shr)), + .AssignBitXor => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignBitXor).?, .xor)), + .AssignDiv => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignDiv).?, .div)), + .AssignSub => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignSub).?, .sub)), + .AssignSubWrap => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignSubWrap).?, .subwrap)), + .AssignMod => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignMod).?, .mod_rem)), + .AssignAdd => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignAdd).?, .add)), + .AssignAddWrap => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignAddWrap).?, .addwrap)), + .AssignMul => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignMul).?, .mul)), + .AssignMulWrap => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignMulWrap).?, .mulwrap)), .Add => return simpleBinOp(mod, scope, rl, node.castTag(.Add).?, .add), .AddWrap => return simpleBinOp(mod, scope, rl, node.castTag(.AddWrap).?, .addwrap), @@ -96,6 +97,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .Unreachable => return unreach(mod, scope, node.castTag(.Unreachable).?), .Return => return ret(mod, scope, node.castTag(.Return).?), .If => return ifExpr(mod, scope, rl, node.castTag(.If).?), + .While => return whileExpr(mod, scope, rl, node.castTag(.While).?), .Period => return rlWrap(mod, scope, rl, try field(mod, scope, node.castTag(.Period).?)), .Deref => return rlWrap(mod, scope, rl, try deref(mod, scope, node.castTag(.Deref).?)), .BoolNot => return rlWrap(mod, scope, rl, try boolNot(mod, scope, node.castTag(.BoolNot).?)), @@ -127,10 +129,7 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block const var_decl_node = statement.castTag(.VarDecl).?; scope = try varDecl(mod, scope, var_decl_node, &block_arena.allocator); }, - .Assign => { - const ass = statement.castTag(.Assign).?; - try assign(mod, scope, ass); - }, + .Assign => try assign(mod, scope, statement.castTag(.Assign).?), .AssignBitAnd => try assignOp(mod, scope, statement.castTag(.AssignBitAnd).?, .bitand), .AssignBitOr => try assignOp(mod, scope, statement.castTag(.AssignBitOr).?, .bitor), .AssignBitShiftLeft => try assignOp(mod, scope, statement.castTag(.AssignBitShiftLeft).?, .shl), @@ -454,6 +453,132 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn return &block.base; } +fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.While) InnerError!*zir.Inst { + if (while_node.payload) |payload| { + return mod.failNode(scope, payload, "TODO implement astgen.whileExpr for optionals", .{}); + } + if (while_node.@"else") |else_node| { + if (else_node.payload) |payload| { + return mod.failNode(scope, payload, "TODO implement astgen.whileExpr for error unions", .{}); + } + } + + var expr_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = scope.decl().?, + .arena = scope.arena(), + .instructions = .{}, + }; + defer expr_scope.instructions.deinit(mod.gpa); + + var loop_scope: Scope.GenZIR = .{ + .parent = &expr_scope.base, + .decl = expr_scope.decl, + .arena = expr_scope.arena, + .instructions = .{}, + }; + defer loop_scope.instructions.deinit(mod.gpa); + + var continue_scope: Scope.GenZIR = .{ + .parent = &loop_scope.base, + .decl = loop_scope.decl, + .arena = loop_scope.arena, + .instructions = .{}, + }; + defer continue_scope.instructions.deinit(mod.gpa); + + const tree = scope.tree(); + const while_src = tree.token_locs[while_node.while_token].start; + const bool_type = try addZIRInstConst(mod, scope, while_src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.bool_type), + }); + const void_type = try addZIRInstConst(mod, scope, while_src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.void_type), + }); + const cond = try expr(mod, &continue_scope.base, .{ .ty = bool_type }, while_node.condition); + + const condbr = try addZIRInstSpecial(mod, &continue_scope.base, while_src, zir.Inst.CondBr, .{ + .condition = cond, + .then_body = undefined, // populated below + .else_body = undefined, // populated below + }, .{}); + const cond_block = try addZIRInstBlock(mod, &loop_scope.base, while_src, .{ + .instructions = try loop_scope.arena.dupe(*zir.Inst, continue_scope.instructions.items), + }); + if (while_node.continue_expr) |cont_expr| { + const cont_expr_result = try expr(mod, &loop_scope.base, .{ .ty = void_type }, cont_expr); + if (!cont_expr_result.tag.isNoReturn()) { + _ = try addZIRNoOp(mod, &loop_scope.base, while_src, .repeat); + } + } else { + _ = try addZIRNoOp(mod, &loop_scope.base, while_src, .repeat); + } + const loop = try addZIRInstLoop(mod, &expr_scope.base, while_src, .{ + .instructions = try expr_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items), + }); + const while_block = try addZIRInstBlock(mod, scope, while_src, .{ + .instructions = try expr_scope.arena.dupe(*zir.Inst, expr_scope.instructions.items), + }); + var then_scope: Scope.GenZIR = .{ + .parent = &continue_scope.base, + .decl = continue_scope.decl, + .arena = continue_scope.arena, + .instructions = .{}, + }; + defer then_scope.instructions.deinit(mod.gpa); + + // Most result location types can be forwarded directly; however + // if we need to write to a pointer which has an inferred type, + // proper type inference requires peer type resolution on the while's + // branches. + const branch_rl: ResultLoc = switch (rl) { + .discard, .none, .ty, .ptr, .lvalue => rl, + .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = while_block }, + }; + + const then_result = try expr(mod, &then_scope.base, branch_rl, while_node.body); + if (!then_result.tag.isNoReturn()) { + const then_src = tree.token_locs[while_node.body.lastToken()].start; + _ = try addZIRInst(mod, &then_scope.base, then_src, zir.Inst.Break, .{ + .block = cond_block, + .operand = then_result, + }, .{}); + } + condbr.positionals.then_body = .{ + .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items), + }; + + var else_scope: Scope.GenZIR = .{ + .parent = &continue_scope.base, + .decl = continue_scope.decl, + .arena = continue_scope.arena, + .instructions = .{}, + }; + defer else_scope.instructions.deinit(mod.gpa); + + if (while_node.@"else") |else_node| { + const else_result = try expr(mod, &else_scope.base, branch_rl, else_node.body); + if (!else_result.tag.isNoReturn()) { + const else_src = tree.token_locs[else_node.body.lastToken()].start; + _ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.Break, .{ + .block = while_block, + .operand = else_result, + }, .{}); + } + } else { + const else_src = tree.token_locs[while_node.lastToken()].start; + _ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.BreakVoid, .{ + .block = while_block, + }, .{}); + } + condbr.positionals.else_body = .{ + .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items), + }; + return &while_block.base; +} + fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[cfe.ltoken].start; @@ -1094,6 +1219,15 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr } } +fn rlWrapVoid(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node, result: void) InnerError!*zir.Inst { + const src = scope.tree().token_locs[node.firstToken()].start; + const void_inst = try addZIRInstConst(mod, scope, src, .{ + .ty = Type.initTag(.void), + .val = Value.initTag(.void_value), + }); + return rlWrap(mod, scope, rl, void_inst); +} + pub fn addZIRInstSpecial( mod: *Module, scope: *Scope, @@ -1211,3 +1345,9 @@ pub fn addZIRInstBlock(mod: *Module, scope: *Scope, src: usize, body: zir.Module const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type; return addZIRInstSpecial(mod, scope, src, zir.Inst.Block, P{ .body = body }, .{}); } + +/// TODO The existence of this function is a workaround for a bug in stage1. +pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Loop { + const P = std.meta.fieldInfo(zir.Inst.Loop, "positionals").field_type; + return addZIRInstSpecial(mod, scope, src, zir.Inst.Loop, P{ .body = body }, .{}); +} diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index a3c95349e1..6e8ab34478 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -23,6 +23,8 @@ pub const BlockData = struct { relocs: std.ArrayListUnmanaged(Reloc) = .{}, }; +pub const LoopData = struct { }; + pub const Reloc = union(enum) { /// The value is an offset into the `Function` `code` from the beginning. /// To perform the reloc, write 32-bit signed little-endian integer @@ -657,6 +659,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .isnonnull => return self.genIsNonNull(inst.castTag(.isnonnull).?), .isnull => return self.genIsNull(inst.castTag(.isnull).?), .load => return self.genLoad(inst.castTag(.load).?), + .loop => return self.genLoop(inst.castTag(.loop).?), .not => return self.genNot(inst.castTag(.not).?), .ptrtoint => return self.genPtrToInt(inst.castTag(.ptrtoint).?), .ref => return self.genRef(inst.castTag(.ref).?), @@ -1346,6 +1349,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } + fn genLoop(self: *Self, inst: *ir.Inst.Loop) !MCValue { + return self.fail(inst.base.src, "TODO codegen loop", .{}); + } + fn genBlock(self: *Self, inst: *ir.Inst.Block) !MCValue { if (inst.base.ty.hasCodeGenBits()) { return self.fail(inst.base.src, "TODO codegen Block with non-void type", .{}); diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 176fd7e303..f4262592de 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -70,6 +70,7 @@ pub const Inst = struct { isnull, /// Read a value from a pointer. load, + loop, ptrtoint, ref, ret, @@ -122,6 +123,7 @@ pub const Inst = struct { .call => Call, .condbr => CondBr, .constant => Constant, + .loop => Loop, }; } @@ -401,6 +403,23 @@ pub const Inst = struct { return null; } }; + + pub const Loop = struct { + pub const base_tag = Tag.loop; + + base: Inst, + body: Body, + /// This memory is reserved for codegen code to do whatever it needs to here. + codegen: codegen.LoopData = .{}, + + pub fn operandCount(self: *const Loop) usize { + return 0; + } + pub fn getOperand(self: *const Loop, index: usize) ?*Inst { + return null; + } + }; + }; pub const Body = struct { diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index d05c676a38..431d510b01 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -1484,9 +1484,6 @@ pub const File = struct { assert(!self.shdr_table_dirty); assert(!self.shstrtab_dirty); assert(!self.debug_strtab_dirty); - assert(!self.offset_table_count_dirty); - const syms_sect = &self.sections.items[self.symtab_section_index.?]; - assert(syms_sect.sh_info == self.local_symbols.items.len); } fn writeDwarfAddrAssumeCapacity(self: *Elf, buf: *std.ArrayList(u8), addr: u64) void { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 29043af840..ba0c05d587 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -151,6 +151,8 @@ pub const Inst = struct { isnonnull, /// Return a boolean true if an optional is null. `x == null` isnull, + /// A labeled block of code that loops forever. + loop, /// Ambiguously remainder division or modulus. If the computation would possibly have /// a different value depending on whether the operation is remainder division or modulus, /// a compile error is emitted. Otherwise the computation is performed. @@ -173,6 +175,8 @@ pub const Inst = struct { /// the memory location is in the stack frame, local to the scope containing the /// instruction. ref, + /// Sends control flow back to the loop block operand. + repeat, /// Obtains a pointer to the return value. ret_ptr, /// Obtains the return type of the in-scope function. @@ -279,7 +283,9 @@ pub const Inst = struct { .declval_in_module => DeclValInModule, .coerce_result_block_ptr => CoerceResultBlockPtr, .compileerror => CompileError, + .loop => Loop, .@"const" => Const, + .repeat => Repeat, .str => Str, .int => Int, .inttype => IntType, @@ -372,10 +378,12 @@ pub const Inst = struct { .breakvoid, .condbr, .compileerror, + .repeat, .@"return", .returnvoid, .unreach_nocheck, .@"unreachable", + .loop, => true, }; } @@ -567,6 +575,16 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const Repeat = struct { + pub const base_tag = Tag.repeat; + base: Inst, + + positionals: struct { + loop: *Loop, + }, + kw_args: struct {}, + }; + pub const Str = struct { pub const base_tag = Tag.str; base: Inst, @@ -587,6 +605,16 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const Loop = struct { + pub const base_tag = Tag.loop; + base: Inst, + + positionals: struct { + body: Module.Body, + }, + kw_args: struct {}, + }; + pub const FieldPtr = struct { pub const base_tag = Tag.fieldptr; base: Inst, @@ -848,12 +876,14 @@ pub const Module = struct { .module = &self, .inst_table = InstPtrTable.init(allocator), .block_table = std.AutoHashMap(*Inst.Block, []const u8).init(allocator), + .loop_table = std.AutoHashMap(*Inst.Loop, []const u8).init(allocator), .arena = std.heap.ArenaAllocator.init(allocator), .indent = 2, }; defer write.arena.deinit(); defer write.inst_table.deinit(); defer write.block_table.deinit(); + defer write.loop_table.deinit(); // First, build a map of *Inst to @ or % indexes try write.inst_table.ensureCapacity(self.decls.len); @@ -882,6 +912,7 @@ const Writer = struct { module: *const Module, inst_table: InstPtrTable, block_table: std.AutoHashMap(*Inst.Block, []const u8), + loop_table: std.AutoHashMap(*Inst.Loop, []const u8), arena: std.heap.ArenaAllocator, indent: usize, @@ -962,6 +993,9 @@ const Writer = struct { if (inst.cast(Inst.Block)) |block| { const name = try std.fmt.allocPrint(&self.arena.allocator, "label_{}", .{i}); try self.block_table.put(block, name); + } else if (inst.cast(Inst.Loop)) |loop| { + const name = try std.fmt.allocPrint(&self.arena.allocator, "loop_{}", .{i}); + try self.loop_table.put(loop, name); } self.indent += 2; try self.writeInstToStream(stream, inst); @@ -980,6 +1014,10 @@ const Writer = struct { const name = self.block_table.get(param).?; return std.zig.renderStringLiteral(name, stream); }, + *Inst.Loop => { + const name = self.loop_table.get(param).?; + return std.zig.renderStringLiteral(name, stream); + }, else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), } } @@ -1016,8 +1054,10 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module .decls = .{}, .unnamed_index = 0, .block_table = std.StringHashMap(*Inst.Block).init(allocator), + .loop_table = std.StringHashMap(*Inst.Loop).init(allocator), }; defer parser.block_table.deinit(); + defer parser.loop_table.deinit(); errdefer parser.arena.deinit(); parser.parseRoot() catch |err| switch (err) { @@ -1044,6 +1084,7 @@ const Parser = struct { error_msg: ?ErrorMsg = null, unnamed_index: usize, block_table: std.StringHashMap(*Inst.Block), + loop_table: std.StringHashMap(*Inst.Loop), const Body = struct { instructions: std.ArrayList(*Inst), @@ -1255,6 +1296,8 @@ const Parser = struct { if (InstType == Inst.Block) { try self.block_table.put(inst_name, inst_specific); + } else if (InstType == Inst.Loop) { + try self.loop_table.put(inst_name, inst_specific); } if (@hasField(InstType, "ty")) { @@ -1366,6 +1409,10 @@ const Parser = struct { const name = try self.parseStringLiteral(); return self.block_table.get(name).?; }, + *Inst.Loop => { + const name = try self.parseStringLiteral(); + return self.loop_table.get(name).?; + }, else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), } return self.fail("TODO parse parameter {}", .{@typeName(T)}); @@ -1431,8 +1478,10 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), .indent = 0, .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), + .loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator), }; defer ctx.block_table.deinit(); + defer ctx.loop_table.deinit(); defer ctx.decls.deinit(allocator); defer ctx.names.deinit(); defer ctx.primitive_table.deinit(); @@ -1456,6 +1505,7 @@ const EmitZIR = struct { primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl), indent: usize, block_table: std.AutoHashMap(*ir.Inst.Block, *Inst.Block), + loop_table: std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop), fn emit(self: *EmitZIR) !void { // Put all the Decls in a list and sort them by name to avoid nondeterminism introduced @@ -1936,6 +1986,31 @@ const EmitZIR = struct { break :blk &new_inst.base; }, + .loop => blk: { + const old_inst = inst.castTag(.loop).?; + const new_inst = try self.arena.allocator.create(Inst.Loop); + + try self.loop_table.put(old_inst, new_inst); + + var loop_body = std.ArrayList(*Inst).init(self.allocator); + defer loop_body.deinit(); + + try self.emitBody(old_inst.body, inst_table, &loop_body); + + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.Loop.base_tag, + }, + .positionals = .{ + .body = .{ .instructions = loop_body.toOwnedSlice() }, + }, + .kw_args = .{}, + }; + + break :blk &new_inst.base; + }, + .brvoid => blk: { const old_inst = inst.cast(ir.Inst.BrVoid).?; const new_block = self.block_table.get(old_inst.block).?; diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index 2b2739a308..e36487e8d5 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -60,6 +60,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! return mod.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int); }, .inttype => return analyzeInstIntType(mod, scope, old_inst.castTag(.inttype).?), + .loop => return analyzeInstLoop(mod, scope, old_inst.castTag(.loop).?), + .repeat => return analyzeInstRepeat(mod, scope, old_inst.castTag(.repeat).?), .param_type => return analyzeInstParamType(mod, scope, old_inst.castTag(.param_type).?), .ptrtoint => return analyzeInstPtrToInt(mod, scope, old_inst.castTag(.ptrtoint).?), .fieldptr => return analyzeInstFieldPtr(mod, scope, old_inst.castTag(.fieldptr).?), @@ -424,6 +426,14 @@ fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!* return mod.addArg(b, inst.base.src, param_type, name); } +fn analyzeInstRepeat(mod: *Module, scope: *Scope, inst: *zir.Inst.Repeat) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO analyze .repeat ZIR", .{}); +} + +fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO analyze .loop ZIR", .{}); +} + fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst { const parent_block = scope.cast(Scope.Block).?;