stage2: astgen for while loops

See #6021
This commit is contained in:
Andrew Kelley 2020-08-12 21:13:07 -07:00
parent 30db5b1fb2
commit de4f3f11f7
6 changed files with 269 additions and 21 deletions

View File

@ -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 }, .{});
}

View File

@ -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", .{});

View File

@ -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 {

View File

@ -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 {

View File

@ -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).?;

View File

@ -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).?;