stage2: *WIP*: rework ZIR memory layout; overhaul source locations

The memory layout for ZIR instructions is completely reworked. See
zir.zig for those changes. Some new types:

 * `zir.Code`: a "finished" set of ZIR instructions. Instead of allocating
   each instruction independently, there is now a Tag and 8 bytes of
   data available for all ZIR instructions. Small instructions fit
   within these 8 bytes; larger ones use 4 bytes for an index into
   `extra`. There is also `string_bytes` so that we can have 4 byte
   references to strings. `zir.Inst.Tag` describes how to interpret
   those 8 bytes of data.
   - This is shared by all `Block` scopes.

 * `Module.WipZirCode`: represents an in-progress `zir.Code`. In this
   structure, the arrays are mutable, and get resized as we add/delete
   things. There is extra state to keep track of things. This struct is
   stored on the stack. Once it is finished, it produces an immutable
   `zir.Code`, which will remain on the heap for the duration of a
   function's existence.
   - This is shared by all `GenZir` scopes.

 * `Sema`: represents in-progress semantic analysis of a `zir.Code`.
   This data is stored on the stack and is shared among all `Block`
   scopes. It is now the main "self" argument to everything in the file
   that was previously named `zir_sema.zig`.
   Additionally, I moved some logic that was in `Module` into here.

`Module.Fn` now stores its parameter names inside the `zir.Code`,
instead of inside ZIR instructions. When the TZIR memory layout
reworking time comes, codegen will be able to reference this data
directly instead of duplicating it.

astgen.zig is (so far) almost entirely untouched, but nearly all of it
will need to be reworked to adhere to this new memory layout structure.

I have no benchmarks to report yet, as I am still working through
compile errors and fixing various things that I broke in this branch.

Overhaul of Source Locations:

Previously we used `usize` everywhere to mean byte offset, but sometimes
also mean other stuff. This was error prone and also made us do
unnecessary work, and store unnecessary bytes in memory.

Now there are more types involved into source locations, and more ways
to describe a source location.

 * AllErrors.Message: embrace the assumption that files always have less
   than 2 << 32 bytes.
 * SrcLoc gets more complicated, to model more complicated source
   locations.
 * Introduce LazySrcLoc, which can model interesting source locations
   with very little stored state. Useful for avoiding doing unnecessary
   work when no compile errors occur.

Also, previously, we had `src: usize` on every ZIR instruction. This is
no longer the case. Each instruction now determines whether it even cares
about source location, and if so, how that source location is stored.
This requires more careful work inside `Sema`, but it results in fewer
bytes stored on the heap, without compromising accuracy and power of
compile error messages.

Miscellaneous:

 * std.zig: string literals have more helpful result values for
   reporting errors. There is now a lower level API and a higher level
   API.
   - side note: I noticed that the string literal logic needs some love.
     There is some unnecessarily hacky code there.
 * cut & pasted some TZIR logic that was in zir.zig to ir.zig. This
   probably broke stuff and needs to get fixed.
 * Removed type/Enum.zig, type/Union.zig, and type/Struct.zig. I don't
   think this quite how this code will be organized. Need some more
   careful planning about how to implement structs, unions, enums. They
   need to be independent Decls, just like a top level function.
This commit is contained in:
Andrew Kelley 2021-03-15 23:38:38 -07:00
parent f16f25047c
commit aef3e534f5
14 changed files with 4833 additions and 4807 deletions

126
BRANCH_TODO Normal file
View File

@ -0,0 +1,126 @@
this is my WIP branch scratch pad, to be deleted before merging into master
Merge TODO list:
* fix discrepancy between TZIR wanting src: usize (byte offset) and Sema
now providing LazySrcLoc
* fix compile errors
* don't have an explicit dbg_stmt zir instruction - instead merge it with
var decl and assignment instructions, etc.
- make it set sema.src where appropriate
* remove the LazySrcLoc.todo tag
* update astgen.zig
* finish updating Sema.zig
* finish implementing SrcLoc byteOffset function
Performance optimizations to look into:
* don't store end index for blocks; rely on last instruction being noreturn
* introduce special form for function call statement with 0 or 1 parameters
* look into not storing the field name of field access as a string in zir
instructions. or, look into introducing interning to string_bytes (local
to the owner Decl), or, look into allowing field access based on a token/node
and have it reference source code bytes. Another idea: null terminated
string variants which avoid having to store the length.
- Look into this for enum literals too
Random snippets of code that I deleted and need to make sure get
re-integrated appropriately:
fn zirArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*Inst {
const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
const param_index = b.instructions.items.len;
const param_count = fn_ty.fnParamLen();
if (param_index >= param_count) {
return mod.fail(scope, inst.base.src, "parameter index {d} outside list of length {d}", .{
param_index,
param_count,
});
}
const param_type = fn_ty.fnParamType(param_index);
const name = try scope.arena().dupeZ(u8, inst.positionals.name);
return mod.addArg(b, inst.base.src, param_type, name);
}
fn zirReturnVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
const b = try mod.requireFunctionBlock(scope, inst.base.src);
if (b.inlining) |inlining| {
// We are inlining a function call; rewrite the `retvoid` as a `breakvoid`.
const void_inst = try mod.constVoid(scope, inst.base.src);
try inlining.merges.results.append(mod.gpa, void_inst);
const br = try mod.addBr(b, inst.base.src, inlining.merges.block_inst, void_inst);
return &br.base;
}
if (b.func) |func| {
// Need to emit a compile error if returning void is not allowed.
const void_inst = try mod.constVoid(scope, inst.base.src);
const fn_ty = func.owner_decl.typed_value.most_recent.typed_value.ty;
const casted_void = try mod.coerce(scope, fn_ty.fnReturnType(), void_inst);
if (casted_void.ty.zigTypeTag() != .Void) {
return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, casted_void);
}
}
return mod.addNoOp(b, inst.base.src, Type.initTag(.noreturn), .retvoid);
}
fn zirReturn(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
const operand = try resolveInst(mod, scope, inst.positionals.operand);
const b = try mod.requireFunctionBlock(scope, inst.base.src);
if (b.inlining) |inlining| {
// We are inlining a function call; rewrite the `ret` as a `break`.
try inlining.merges.results.append(mod.gpa, operand);
const br = try mod.addBr(b, inst.base.src, inlining.merges.block_inst, operand);
return &br.base;
}
return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand);
}
fn zirPrimitive(mod: *Module, scope: *Scope, primitive: *zir.Inst.Primitive) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
return mod.constInst(scope, primitive.base.src, primitive.positionals.tag.toTypedValue());
}
/// Each Decl gets its own string interning, in order to avoid contention when
/// using multiple threads to analyze Decls in parallel. Any particular Decl will only
/// be touched by a single thread at one time.
strings: StringTable = .{},
/// The string memory referenced here is stored inside the Decl's arena.
pub const StringTable = std.StringArrayHashMapUnmanaged(void);
pub fn errSrcLoc(mod: *Module, scope: *Scope, src: LazySrcLoc) SrcLoc {
const file_scope = scope.getFileScope();
switch (src) {
.byte_offset => |off| return .{
.file_scope = file_scope,
.byte_offset = off,
},
.token_offset => |off| {
@panic("TODO errSrcLoc for token_offset");
},
.node_offset => |off| {
@panic("TODO errSrcLoc for node_offset");
},
.node_offset_var_decl_ty => |off| {
@panic("TODO errSrcLoc for node_offset_var_decl_ty");
},
}
}

View File

@ -11,7 +11,7 @@ pub const Tokenizer = tokenizer.Tokenizer;
pub const fmtId = @import("zig/fmt.zig").fmtId;
pub const fmtEscapes = @import("zig/fmt.zig").fmtEscapes;
pub const parse = @import("zig/parse.zig").parse;
pub const parseStringLiteral = @import("zig/string_literal.zig").parse;
pub const string_literal = @import("zig/string_literal.zig");
pub const ast = @import("zig/ast.zig");
pub const system = @import("zig/system.zig");
pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget;

View File

@ -6,112 +6,143 @@
const std = @import("../std.zig");
const assert = std.debug.assert;
const State = enum {
Start,
Backslash,
};
pub const ParseError = error{
OutOfMemory,
/// When this is returned, index will be the position of the character.
InvalidCharacter,
InvalidStringLiteral,
};
/// caller owns returned memory
pub fn parse(
allocator: *std.mem.Allocator,
bytes: []const u8,
bad_index: *usize, // populated if error.InvalidCharacter is returned
) ParseError![]u8 {
pub const Result = union(enum) {
success,
/// Found an invalid character at this index.
invalid_character: usize,
/// Expected hex digits at this index.
expected_hex_digits: usize,
/// Invalid hex digits at this index.
invalid_hex_escape: usize,
/// Invalid unicode escape at this index.
invalid_unicode_escape: usize,
/// The left brace at this index is missing a matching right brace.
missing_matching_brace: usize,
/// Expected unicode digits at this index.
expected_unicode_digits: usize,
};
/// Parses `bytes` as a Zig string literal and appends the result to `buf`.
/// Asserts `bytes` has '"' at beginning and end.
pub fn parseAppend(buf: *std.ArrayList(u8), bytes: []const u8) error{OutOfMemory}!Result {
assert(bytes.len >= 2 and bytes[0] == '"' and bytes[bytes.len - 1] == '"');
var list = std.ArrayList(u8).init(allocator);
errdefer list.deinit();
const slice = bytes[1..];
try list.ensureCapacity(slice.len - 1);
const prev_len = buf.items.len;
try buf.ensureCapacity(prev_len + slice.len - 1);
errdefer buf.shrinkRetainingCapacity(prev_len);
const State = enum {
Start,
Backslash,
};
var state = State.Start;
var index: usize = 0;
while (index < slice.len) : (index += 1) {
while (true) : (index += 1) {
const b = slice[index];
switch (state) {
State.Start => switch (b) {
'\\' => state = State.Backslash,
'\n' => {
bad_index.* = index;
return error.InvalidCharacter;
return Result{ .invalid_character = index };
},
'"' => return list.toOwnedSlice(),
else => try list.append(b),
'"' => return Result.success,
else => try buf.append(b),
},
State.Backslash => switch (b) {
'n' => {
try list.append('\n');
try buf.append('\n');
state = State.Start;
},
'r' => {
try list.append('\r');
try buf.append('\r');
state = State.Start;
},
'\\' => {
try list.append('\\');
try buf.append('\\');
state = State.Start;
},
't' => {
try list.append('\t');
try buf.append('\t');
state = State.Start;
},
'\'' => {
try list.append('\'');
try buf.append('\'');
state = State.Start;
},
'"' => {
try list.append('"');
try buf.append('"');
state = State.Start;
},
'x' => {
// TODO: add more/better/broader tests for this.
const index_continue = index + 3;
if (slice.len >= index_continue)
if (std.fmt.parseUnsigned(u8, slice[index + 1 .. index_continue], 16)) |char| {
try list.append(char);
state = State.Start;
index = index_continue - 1; // loop-header increments again
continue;
} else |_| {};
bad_index.* = index;
return error.InvalidCharacter;
if (slice.len < index_continue) {
return Result{ .expected_hex_digits = index };
}
if (std.fmt.parseUnsigned(u8, slice[index + 1 .. index_continue], 16)) |byte| {
try buf.append(byte);
state = State.Start;
index = index_continue - 1; // loop-header increments again
} else |err| switch (err) {
error.Overflow => unreachable, // 2 digits base 16 fits in a u8.
error.InvalidCharacter => {
return Result{ .invalid_hex_escape = index + 1 };
},
}
},
'u' => {
// TODO: add more/better/broader tests for this.
if (slice.len > index + 2 and slice[index + 1] == '{')
// TODO: we are already inside a nice, clean state machine... use it
// instead of this hacky code.
if (slice.len > index + 2 and slice[index + 1] == '{') {
if (std.mem.indexOfScalarPos(u8, slice[0..std.math.min(index + 9, slice.len)], index + 3, '}')) |index_end| {
const hex_str = slice[index + 2 .. index_end];
if (std.fmt.parseUnsigned(u32, hex_str, 16)) |uint| {
if (uint <= 0x10ffff) {
try list.appendSlice(std.mem.toBytes(uint)[0..]);
try buf.appendSlice(std.mem.toBytes(uint)[0..]);
state = State.Start;
index = index_end; // loop-header increments
continue;
}
} else |_| {}
};
bad_index.* = index;
return error.InvalidCharacter;
} else |err| switch (err) {
error.Overflow => unreachable,
error.InvalidCharacter => {
return Result{ .invalid_unicode_escape = index + 1 };
},
}
} else {
return Result{ .missing_matching_rbrace = index + 1 };
}
} else {
return Result{ .expected_unicode_digits = index };
}
},
else => {
bad_index.* = index;
return error.InvalidCharacter;
return Result{ .invalid_character = index };
},
},
}
} else unreachable; // TODO should not need else unreachable on while(true)
}
/// Higher level API. Does not return extra info about parse errors.
/// Caller owns returned memory.
pub fn parseAlloc(allocator: *std.mem.Allocator, bytes: []const u8) ParseError![]u8 {
var buf = std.ArrayList(u8).init(allocator);
defer buf.deinit();
switch (try parseAppend(&buf, bytes)) {
.success => return buf.toOwnedSlice(),
else => return error.InvalidStringLiteral,
}
unreachable;
}
test "parse" {
@ -121,9 +152,8 @@ test "parse" {
var fixed_buf_mem: [32]u8 = undefined;
var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(fixed_buf_mem[0..]);
var alloc = &fixed_buf_alloc.allocator;
var bad_index: usize = undefined;
expect(eql(u8, "foo", try parse(alloc, "\"foo\"", &bad_index)));
expect(eql(u8, "foo", try parse(alloc, "\"f\x6f\x6f\"", &bad_index)));
expect(eql(u8, "f💯", try parse(alloc, "\"f\u{1f4af}\"", &bad_index)));
expect(eql(u8, "foo", try parseAlloc(alloc, "\"foo\"")));
expect(eql(u8, "foo", try parseAlloc(alloc, "\"f\x6f\x6f\"")));
expect(eql(u8, "f💯", try parseAlloc(alloc, "\"f\u{1f4af}\"")));
}

View File

@ -259,7 +259,7 @@ pub const CObject = struct {
/// To support incremental compilation, errors are stored in various places
/// so that they can be created and destroyed appropriately. This structure
/// is used to collect all the errors from the various places into one
/// convenient place for API users to consume. It is allocated into 1 heap
/// convenient place for API users to consume. It is allocated into 1 arena
/// and freed all at once.
pub const AllErrors = struct {
arena: std.heap.ArenaAllocator.State,
@ -267,11 +267,11 @@ pub const AllErrors = struct {
pub const Message = union(enum) {
src: struct {
src_path: []const u8,
line: usize,
column: usize,
byte_offset: usize,
msg: []const u8,
src_path: []const u8,
line: u32,
column: u32,
byte_offset: u32,
notes: []Message = &.{},
},
plain: struct {
@ -316,29 +316,31 @@ pub const AllErrors = struct {
const notes = try arena.allocator.alloc(Message, module_err_msg.notes.len);
for (notes) |*note, i| {
const module_note = module_err_msg.notes[i];
const source = try module_note.src_loc.file_scope.getSource(module);
const loc = std.zig.findLineColumn(source, module_note.src_loc.byte_offset);
const sub_file_path = module_note.src_loc.file_scope.sub_file_path;
const source = try module_note.src_loc.fileScope().getSource(module);
const byte_offset = try module_note.src_loc.byteOffset(module);
const loc = std.zig.findLineColumn(source, byte_offset);
const sub_file_path = module_note.src_loc.fileScope().sub_file_path;
note.* = .{
.src = .{
.src_path = try arena.allocator.dupe(u8, sub_file_path),
.msg = try arena.allocator.dupe(u8, module_note.msg),
.byte_offset = module_note.src_loc.byte_offset,
.line = loc.line,
.column = loc.column,
.byte_offset = byte_offset,
.line = @intCast(u32, loc.line),
.column = @intCast(u32, loc.column),
},
};
}
const source = try module_err_msg.src_loc.file_scope.getSource(module);
const loc = std.zig.findLineColumn(source, module_err_msg.src_loc.byte_offset);
const sub_file_path = module_err_msg.src_loc.file_scope.sub_file_path;
const source = try module_err_msg.src_loc.fileScope().getSource(module);
const byte_offset = try module_err_msg.src_loc.byteOffset(module);
const loc = std.zig.findLineColumn(source, byte_offset);
const sub_file_path = module_err_msg.src_loc.fileScope().sub_file_path;
try errors.append(.{
.src = .{
.src_path = try arena.allocator.dupe(u8, sub_file_path),
.msg = try arena.allocator.dupe(u8, module_err_msg.msg),
.byte_offset = module_err_msg.src_loc.byte_offset,
.line = loc.line,
.column = loc.column,
.byte_offset = byte_offset,
.line = @intCast(u32, loc.line),
.column = @intCast(u32, loc.column),
.notes = notes,
},
});

File diff suppressed because it is too large Load Diff

View File

@ -25,21 +25,22 @@ pub const ResultLoc = union(enum) {
/// of an assignment uses this kind of result location.
ref,
/// The expression will be coerced into this type, but it will be evaluated as an rvalue.
ty: *zir.Inst,
ty: zir.Inst.Index,
/// The expression must store its result into this typed pointer. The result instruction
/// from the expression must be ignored.
ptr: *zir.Inst,
ptr: zir.Inst.Index,
/// The expression must store its result into this allocation, which has an inferred type.
/// The result instruction from the expression must be ignored.
inferred_ptr: *zir.Inst.Tag.alloc_inferred.Type(),
/// Always an instruction with tag `alloc_inferred`.
inferred_ptr: zir.Inst.Index,
/// The expression must store its result into this pointer, which is a typed pointer that
/// has been bitcasted to whatever the expression's type is.
/// The result instruction from the expression must be ignored.
bitcasted_ptr: *zir.Inst.UnOp,
bitcasted_ptr: zir.Inst.Index,
/// There is a pointer for the expression to store its result into, however, its type
/// is inferred based on peer type resolution for a `zir.Inst.Block`.
/// The result instruction from the expression must be ignored.
block_ptr: *Module.Scope.GenZIR,
block_ptr: *Module.Scope.GenZir,
pub const Strategy = struct {
elide_store_to_block_ptr_instructions: bool,
@ -369,10 +370,10 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) In
.call_one, .call_one_comma, .async_call_one, .async_call_one_comma => {
var params: [1]ast.Node.Index = undefined;
return callExpr(mod, scope, rl, tree.callOne(&params, node));
return callExpr(mod, scope, rl, node, tree.callOne(&params, node));
},
.call, .call_comma, .async_call, .async_call_comma => {
return callExpr(mod, scope, rl, tree.callFull(node));
return callExpr(mod, scope, rl, node, tree.callFull(node));
},
.unreachable_literal => {
@ -487,9 +488,12 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) In
},
.enum_literal => {
const ident_token = main_tokens[node];
const name = try mod.identifierTokenString(scope, ident_token);
const src = token_starts[ident_token];
const result = try addZIRInst(mod, scope, src, zir.Inst.EnumLiteral, .{ .name = name }, .{});
const gen_zir = scope.getGenZir();
const string_bytes = &gen_zir.zir_exec.string_bytes;
const str_index = string_bytes.items.len;
try mod.appendIdentStr(scope, ident_token, string_bytes);
const str_len = string_bytes.items.len - str_index;
const result = try gen_zir.addStr(.enum_literal, str_index, str_len);
return rvalue(mod, scope, rl, result);
},
.error_value => {
@ -679,7 +683,7 @@ pub fn comptimeExpr(
const token_starts = tree.tokens.items(.start);
// Make a scope to collect generated instructions in the sub-expression.
var block_scope: Scope.GenZIR = .{
var block_scope: Scope.GenZir = .{
.parent = parent_scope,
.decl = parent_scope.ownerDecl().?,
.arena = parent_scope.arena(),
@ -720,7 +724,7 @@ fn breakExpr(
while (true) {
switch (scope.tag) {
.gen_zir => {
const gen_zir = scope.cast(Scope.GenZIR).?;
const gen_zir = scope.cast(Scope.GenZir).?;
const block_inst = blk: {
if (break_label != 0) {
@ -755,7 +759,7 @@ fn breakExpr(
try gen_zir.labeled_breaks.append(mod.gpa, br.castTag(.@"break").?);
if (have_store_to_block) {
const inst_list = parent_scope.getGenZIR().instructions.items;
const inst_list = parent_scope.getGenZir().instructions.items;
const last_inst = inst_list[inst_list.len - 2];
const store_inst = last_inst.castTag(.store_to_block_ptr).?;
assert(store_inst.positionals.lhs == gen_zir.rl_ptr.?);
@ -797,7 +801,7 @@ fn continueExpr(
while (true) {
switch (scope.tag) {
.gen_zir => {
const gen_zir = scope.cast(Scope.GenZIR).?;
const gen_zir = scope.cast(Scope.GenZir).?;
const continue_block = gen_zir.continue_block orelse {
scope = gen_zir.parent;
continue;
@ -864,7 +868,7 @@ fn checkLabelRedefinition(mod: *Module, parent_scope: *Scope, label: ast.TokenIn
while (true) {
switch (scope.tag) {
.gen_zir => {
const gen_zir = scope.cast(Scope.GenZIR).?;
const gen_zir = scope.cast(Scope.GenZir).?;
if (gen_zir.label) |prev_label| {
if (try tokenIdentEql(mod, parent_scope, label, prev_label.token)) {
const tree = parent_scope.tree();
@ -931,9 +935,9 @@ fn labeledBlockExpr(
try checkLabelRedefinition(mod, parent_scope, label_token);
// Create the Block ZIR instruction so that we can put it into the GenZIR struct
// Create the Block ZIR instruction so that we can put it into the GenZir struct
// so that break statements can reference it.
const gen_zir = parent_scope.getGenZIR();
const gen_zir = parent_scope.getGenZir();
const block_inst = try gen_zir.arena.create(zir.Inst.Block);
block_inst.* = .{
.base = .{
@ -946,14 +950,14 @@ fn labeledBlockExpr(
.kw_args = .{},
};
var block_scope: Scope.GenZIR = .{
var block_scope: Scope.GenZir = .{
.parent = parent_scope,
.decl = parent_scope.ownerDecl().?,
.arena = gen_zir.arena,
.force_comptime = parent_scope.isComptime(),
.instructions = .{},
// TODO @as here is working around a stage1 miscompilation bug :(
.label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{
.label = @as(?Scope.GenZir.Label, Scope.GenZir.Label{
.token = label_token,
.block_inst = block_inst,
}),
@ -1107,8 +1111,8 @@ fn varDecl(
}
s = local_ptr.parent;
},
.gen_zir => s = s.cast(Scope.GenZIR).?.parent,
.gen_suspend => s = s.cast(Scope.GenZIR).?.parent,
.gen_zir => s = s.cast(Scope.GenZir).?.parent,
.gen_suspend => s = s.cast(Scope.GenZir).?.parent,
.gen_nosuspend => s = s.cast(Scope.Nosuspend).?.parent,
else => break,
};
@ -1137,7 +1141,7 @@ fn varDecl(
const sub_scope = try block_arena.create(Scope.LocalVal);
sub_scope.* = .{
.parent = scope,
.gen_zir = scope.getGenZIR(),
.gen_zir = scope.getGenZir(),
.name = ident_name,
.inst = init_inst,
};
@ -1146,7 +1150,7 @@ fn varDecl(
// Detect whether the initialization expression actually uses the
// result location pointer.
var init_scope: Scope.GenZIR = .{
var init_scope: Scope.GenZir = .{
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
@ -1168,7 +1172,7 @@ fn varDecl(
}
const init_result_loc: ResultLoc = .{ .block_ptr = &init_scope };
const init_inst = try expr(mod, &init_scope.base, init_result_loc, var_decl.ast.init_node);
const parent_zir = &scope.getGenZIR().instructions;
const parent_zir = &scope.getGenZir().instructions;
if (init_scope.rvalue_rl_count == 1) {
// Result location pointer not used. We don't need an alloc for this
// const local, and type inference becomes trivial.
@ -1192,7 +1196,7 @@ fn varDecl(
const sub_scope = try block_arena.create(Scope.LocalVal);
sub_scope.* = .{
.parent = scope,
.gen_zir = scope.getGenZIR(),
.gen_zir = scope.getGenZir(),
.name = ident_name,
.inst = casted_init,
};
@ -1219,7 +1223,7 @@ fn varDecl(
const sub_scope = try block_arena.create(Scope.LocalPtr);
sub_scope.* = .{
.parent = scope,
.gen_zir = scope.getGenZIR(),
.gen_zir = scope.getGenZir(),
.name = ident_name,
.ptr = init_scope.rl_ptr.?,
};
@ -1246,7 +1250,7 @@ fn varDecl(
const sub_scope = try block_arena.create(Scope.LocalPtr);
sub_scope.* = .{
.parent = scope,
.gen_zir = scope.getGenZIR(),
.gen_zir = scope.getGenZir(),
.name = ident_name,
.ptr = var_data.alloc,
};
@ -1446,203 +1450,13 @@ fn arrayTypeSentinel(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.
return rvalue(mod, scope, rl, result);
}
fn containerField(
mod: *Module,
scope: *Scope,
field: ast.full.ContainerField,
) InnerError!*zir.Inst {
const tree = scope.tree();
const token_starts = tree.tokens.items(.start);
const src = token_starts[field.ast.name_token];
const name = try mod.identifierTokenString(scope, field.ast.name_token);
if (field.comptime_token == null and field.ast.value_expr == 0 and field.ast.align_expr == 0) {
if (field.ast.type_expr != 0) {
const ty = try typeExpr(mod, scope, field.ast.type_expr);
return addZIRInst(mod, scope, src, zir.Inst.ContainerFieldTyped, .{
.bytes = name,
.ty = ty,
}, .{});
} else {
return addZIRInst(mod, scope, src, zir.Inst.ContainerFieldNamed, .{
.bytes = name,
}, .{});
}
}
const ty = if (field.ast.type_expr != 0) try typeExpr(mod, scope, field.ast.type_expr) else null;
// TODO result location should be alignment type
const alignment = if (field.ast.align_expr != 0) try expr(mod, scope, .none, field.ast.align_expr) else null;
// TODO result location should be the field type
const init = if (field.ast.value_expr != 0) try expr(mod, scope, .none, field.ast.value_expr) else null;
return addZIRInst(mod, scope, src, zir.Inst.ContainerField, .{
.bytes = name,
}, .{
.ty = ty,
.init = init,
.alignment = alignment,
.is_comptime = field.comptime_token != null,
});
}
fn containerDecl(
mod: *Module,
scope: *Scope,
rl: ResultLoc,
container_decl: ast.full.ContainerDecl,
) InnerError!*zir.Inst {
const tree = scope.tree();
const token_starts = tree.tokens.items(.start);
const node_tags = tree.nodes.items(.tag);
const token_tags = tree.tokens.items(.tag);
const src = token_starts[container_decl.ast.main_token];
var gen_scope: Scope.GenZIR = .{
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
.force_comptime = scope.isComptime(),
.instructions = .{},
};
defer gen_scope.instructions.deinit(mod.gpa);
var fields = std.ArrayList(*zir.Inst).init(mod.gpa);
defer fields.deinit();
for (container_decl.ast.members) |member| {
// TODO just handle these cases differently since they end up with different ZIR
// instructions anyway. It will be simpler & have fewer branches.
const field = switch (node_tags[member]) {
.container_field_init => try containerField(mod, &gen_scope.base, tree.containerFieldInit(member)),
.container_field_align => try containerField(mod, &gen_scope.base, tree.containerFieldAlign(member)),
.container_field => try containerField(mod, &gen_scope.base, tree.containerField(member)),
else => continue,
};
try fields.append(field);
}
var decl_arena = std.heap.ArenaAllocator.init(mod.gpa);
errdefer decl_arena.deinit();
const arena = &decl_arena.allocator;
var layout: std.builtin.TypeInfo.ContainerLayout = .Auto;
if (container_decl.layout_token) |some| switch (token_tags[some]) {
.keyword_extern => layout = .Extern,
.keyword_packed => layout = .Packed,
else => unreachable,
};
// TODO this implementation is incorrect. The types must be created in semantic
// analysis, not astgen, because the same ZIR is re-used for multiple inline function calls,
// comptime function calls, and generic function instantiations, and these
// must result in different instances of container types.
const container_type = switch (token_tags[container_decl.ast.main_token]) {
.keyword_enum => blk: {
const tag_type: ?*zir.Inst = if (container_decl.ast.arg != 0)
try typeExpr(mod, &gen_scope.base, container_decl.ast.arg)
else
null;
const inst = try addZIRInst(mod, &gen_scope.base, src, zir.Inst.EnumType, .{
.fields = try arena.dupe(*zir.Inst, fields.items),
}, .{
.layout = layout,
.tag_type = tag_type,
});
const enum_type = try arena.create(Type.Payload.Enum);
enum_type.* = .{
.analysis = .{
.queued = .{
.body = .{ .instructions = try arena.dupe(*zir.Inst, gen_scope.instructions.items) },
.inst = inst,
},
},
.scope = .{
.file_scope = scope.getFileScope(),
.ty = Type.initPayload(&enum_type.base),
},
};
break :blk Type.initPayload(&enum_type.base);
},
.keyword_struct => blk: {
assert(container_decl.ast.arg == 0);
const inst = try addZIRInst(mod, &gen_scope.base, src, zir.Inst.StructType, .{
.fields = try arena.dupe(*zir.Inst, fields.items),
}, .{
.layout = layout,
});
const struct_type = try arena.create(Type.Payload.Struct);
struct_type.* = .{
.analysis = .{
.queued = .{
.body = .{ .instructions = try arena.dupe(*zir.Inst, gen_scope.instructions.items) },
.inst = inst,
},
},
.scope = .{
.file_scope = scope.getFileScope(),
.ty = Type.initPayload(&struct_type.base),
},
};
break :blk Type.initPayload(&struct_type.base);
},
.keyword_union => blk: {
const init_inst: ?*zir.Inst = if (container_decl.ast.arg != 0)
try typeExpr(mod, &gen_scope.base, container_decl.ast.arg)
else
null;
const has_enum_token = container_decl.ast.enum_token != null;
const inst = try addZIRInst(mod, &gen_scope.base, src, zir.Inst.UnionType, .{
.fields = try arena.dupe(*zir.Inst, fields.items),
}, .{
.layout = layout,
.has_enum_token = has_enum_token,
.init_inst = init_inst,
});
const union_type = try arena.create(Type.Payload.Union);
union_type.* = .{
.analysis = .{
.queued = .{
.body = .{ .instructions = try arena.dupe(*zir.Inst, gen_scope.instructions.items) },
.inst = inst,
},
},
.scope = .{
.file_scope = scope.getFileScope(),
.ty = Type.initPayload(&union_type.base),
},
};
break :blk Type.initPayload(&union_type.base);
},
.keyword_opaque => blk: {
if (fields.items.len > 0) {
return mod.fail(scope, fields.items[0].src, "opaque types cannot have fields", .{});
}
const opaque_type = try arena.create(Type.Payload.Opaque);
opaque_type.* = .{
.scope = .{
.file_scope = scope.getFileScope(),
.ty = Type.initPayload(&opaque_type.base),
},
};
break :blk Type.initPayload(&opaque_type.base);
},
else => unreachable,
};
const val = try Value.Tag.ty.create(arena, container_type);
const decl = try mod.createContainerDecl(scope, container_decl.ast.main_token, &decl_arena, .{
.ty = Type.initTag(.type),
.val = val,
});
if (rl == .ref) {
return addZIRInst(mod, scope, src, zir.Inst.DeclRef, .{ .decl = decl }, .{});
} else {
return rvalue(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclVal, .{
.decl = decl,
}, .{}));
}
return mod.failTok(scope, container_decl.ast.main_token, "TODO implement container decls", .{});
}
fn errorSetDecl(
@ -1709,7 +1523,7 @@ fn orelseCatchExpr(
const src = token_starts[op_token];
var block_scope: Scope.GenZIR = .{
var block_scope: Scope.GenZir = .{
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
@ -1738,7 +1552,7 @@ fn orelseCatchExpr(
.instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
});
var then_scope: Scope.GenZIR = .{
var then_scope: Scope.GenZir = .{
.parent = &block_scope.base,
.decl = block_scope.decl,
.arena = block_scope.arena,
@ -1766,7 +1580,7 @@ fn orelseCatchExpr(
block_scope.break_count += 1;
const then_result = try expr(mod, then_sub_scope, block_scope.break_result_loc, rhs);
var else_scope: Scope.GenZIR = .{
var else_scope: Scope.GenZir = .{
.parent = &block_scope.base,
.decl = block_scope.decl,
.arena = block_scope.arena,
@ -1804,9 +1618,9 @@ fn finishThenElseBlock(
mod: *Module,
parent_scope: *Scope,
rl: ResultLoc,
block_scope: *Scope.GenZIR,
then_scope: *Scope.GenZIR,
else_scope: *Scope.GenZIR,
block_scope: *Scope.GenZir,
then_scope: *Scope.GenZir,
else_scope: *Scope.GenZir,
then_body: *zir.Body,
else_body: *zir.Body,
then_src: usize,
@ -2023,7 +1837,7 @@ fn boolBinOp(
.val = Value.initTag(.bool_type),
});
var block_scope: Scope.GenZIR = .{
var block_scope: Scope.GenZir = .{
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
@ -2043,7 +1857,7 @@ fn boolBinOp(
.instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
});
var rhs_scope: Scope.GenZIR = .{
var rhs_scope: Scope.GenZir = .{
.parent = scope,
.decl = block_scope.decl,
.arena = block_scope.arena,
@ -2058,7 +1872,7 @@ fn boolBinOp(
.operand = rhs,
}, .{});
var const_scope: Scope.GenZIR = .{
var const_scope: Scope.GenZir = .{
.parent = scope,
.decl = block_scope.decl,
.arena = block_scope.arena,
@ -2100,7 +1914,7 @@ fn ifExpr(
rl: ResultLoc,
if_full: ast.full.If,
) InnerError!*zir.Inst {
var block_scope: Scope.GenZIR = .{
var block_scope: Scope.GenZir = .{
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
@ -2142,7 +1956,7 @@ fn ifExpr(
});
const then_src = token_starts[tree.lastToken(if_full.ast.then_expr)];
var then_scope: Scope.GenZIR = .{
var then_scope: Scope.GenZir = .{
.parent = scope,
.decl = block_scope.decl,
.arena = block_scope.arena,
@ -2160,7 +1974,7 @@ fn ifExpr(
// instructions into place until we know whether to keep store_to_block_ptr
// instructions or not.
var else_scope: Scope.GenZIR = .{
var else_scope: Scope.GenZir = .{
.parent = scope,
.decl = block_scope.decl,
.arena = block_scope.arena,
@ -2201,7 +2015,7 @@ fn ifExpr(
}
/// Expects to find exactly 1 .store_to_block_ptr instruction.
fn copyBodyWithElidedStoreBlockPtr(body: *zir.Body, scope: Module.Scope.GenZIR) !void {
fn copyBodyWithElidedStoreBlockPtr(body: *zir.Body, scope: Module.Scope.GenZir) !void {
body.* = .{
.instructions = try scope.arena.alloc(*zir.Inst, scope.instructions.items.len - 1),
};
@ -2215,7 +2029,7 @@ fn copyBodyWithElidedStoreBlockPtr(body: *zir.Body, scope: Module.Scope.GenZIR)
assert(dst_index == body.instructions.len);
}
fn copyBodyNoEliding(body: *zir.Body, scope: Module.Scope.GenZIR) !void {
fn copyBodyNoEliding(body: *zir.Body, scope: Module.Scope.GenZir) !void {
body.* = .{
.instructions = try scope.arena.dupe(*zir.Inst, scope.instructions.items),
};
@ -2234,7 +2048,7 @@ fn whileExpr(
return mod.failTok(scope, inline_token, "TODO inline while", .{});
}
var loop_scope: Scope.GenZIR = .{
var loop_scope: Scope.GenZir = .{
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
@ -2244,7 +2058,7 @@ fn whileExpr(
setBlockResultLoc(&loop_scope, rl);
defer loop_scope.instructions.deinit(mod.gpa);
var continue_scope: Scope.GenZIR = .{
var continue_scope: Scope.GenZir = .{
.parent = &loop_scope.base,
.decl = loop_scope.decl,
.arena = loop_scope.arena,
@ -2311,14 +2125,14 @@ fn whileExpr(
loop_scope.break_block = while_block;
loop_scope.continue_block = cond_block;
if (while_full.label_token) |label_token| {
loop_scope.label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{
loop_scope.label = @as(?Scope.GenZir.Label, Scope.GenZir.Label{
.token = label_token,
.block_inst = while_block,
});
}
const then_src = token_starts[tree.lastToken(while_full.ast.then_expr)];
var then_scope: Scope.GenZIR = .{
var then_scope: Scope.GenZir = .{
.parent = &continue_scope.base,
.decl = continue_scope.decl,
.arena = continue_scope.arena,
@ -2332,7 +2146,7 @@ fn whileExpr(
loop_scope.break_count += 1;
const then_result = try expr(mod, then_sub_scope, loop_scope.break_result_loc, while_full.ast.then_expr);
var else_scope: Scope.GenZIR = .{
var else_scope: Scope.GenZir = .{
.parent = &continue_scope.base,
.decl = continue_scope.decl,
.arena = continue_scope.arena,
@ -2416,7 +2230,7 @@ fn forExpr(
const cond_src = token_starts[tree.firstToken(for_full.ast.cond_expr)];
const len = try addZIRUnOp(mod, scope, cond_src, .indexable_ptr_len, array_ptr);
var loop_scope: Scope.GenZIR = .{
var loop_scope: Scope.GenZir = .{
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
@ -2426,7 +2240,7 @@ fn forExpr(
setBlockResultLoc(&loop_scope, rl);
defer loop_scope.instructions.deinit(mod.gpa);
var cond_scope: Scope.GenZIR = .{
var cond_scope: Scope.GenZir = .{
.parent = &loop_scope.base,
.decl = loop_scope.decl,
.arena = loop_scope.arena,
@ -2476,7 +2290,7 @@ fn forExpr(
loop_scope.break_block = for_block;
loop_scope.continue_block = cond_block;
if (for_full.label_token) |label_token| {
loop_scope.label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{
loop_scope.label = @as(?Scope.GenZir.Label, Scope.GenZir.Label{
.token = label_token,
.block_inst = for_block,
});
@ -2484,7 +2298,7 @@ fn forExpr(
// while body
const then_src = token_starts[tree.lastToken(for_full.ast.then_expr)];
var then_scope: Scope.GenZIR = .{
var then_scope: Scope.GenZir = .{
.parent = &cond_scope.base,
.decl = cond_scope.decl,
.arena = cond_scope.arena,
@ -2529,7 +2343,7 @@ fn forExpr(
const then_result = try expr(mod, then_sub_scope, loop_scope.break_result_loc, for_full.ast.then_expr);
// else branch
var else_scope: Scope.GenZIR = .{
var else_scope: Scope.GenZir = .{
.parent = &cond_scope.base,
.decl = cond_scope.decl,
.arena = cond_scope.arena,
@ -2609,7 +2423,7 @@ fn switchExpr(
const switch_src = token_starts[switch_token];
var block_scope: Scope.GenZIR = .{
var block_scope: Scope.GenZir = .{
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
@ -2748,7 +2562,7 @@ fn switchExpr(
.instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
});
var case_scope: Scope.GenZIR = .{
var case_scope: Scope.GenZir = .{
.parent = scope,
.decl = block_scope.decl,
.arena = block_scope.arena,
@ -2757,7 +2571,7 @@ fn switchExpr(
};
defer case_scope.instructions.deinit(mod.gpa);
var else_scope: Scope.GenZIR = .{
var else_scope: Scope.GenZir = .{
.parent = scope,
.decl = case_scope.decl,
.arena = case_scope.arena,
@ -2966,12 +2780,8 @@ fn identifier(
return mod.failNode(scope, ident, "TODO implement '_' identifier", .{});
}
if (simple_types.get(ident_name)) |val_tag| {
const result = try addZIRInstConst(mod, scope, src, TypedValue{
.ty = Type.initTag(.type),
.val = Value.initTag(val_tag),
});
return rvalue(mod, scope, rl, result);
if (simple_types.get(ident_name)) |zir_const_tag| {
return rvalue(mod, scope, rl, @enumToInt(zir_const_tag));
}
if (ident_name.len >= 2) integer: {
@ -3030,8 +2840,8 @@ fn identifier(
}
s = local_ptr.parent;
},
.gen_zir => s = s.cast(Scope.GenZIR).?.parent,
.gen_suspend => s = s.cast(Scope.GenZIR).?.parent,
.gen_zir => s = s.cast(Scope.GenZir).?.parent,
.gen_suspend => s = s.cast(Scope.GenZir).?.parent,
.gen_nosuspend => s = s.cast(Scope.Nosuspend).?.parent,
else => break,
};
@ -3166,33 +2976,16 @@ fn integerLiteral(
rl: ResultLoc,
int_lit: ast.Node.Index,
) InnerError!*zir.Inst {
const arena = scope.arena();
const tree = scope.tree();
const main_tokens = tree.nodes.items(.main_token);
const token_starts = tree.tokens.items(.start);
const int_token = main_tokens[int_lit];
const prefixed_bytes = tree.tokenSlice(int_token);
const base: u8 = if (mem.startsWith(u8, prefixed_bytes, "0x"))
16
else if (mem.startsWith(u8, prefixed_bytes, "0o"))
8
else if (mem.startsWith(u8, prefixed_bytes, "0b"))
2
else
@as(u8, 10);
const bytes = if (base == 10)
prefixed_bytes
else
prefixed_bytes[2..];
if (std.fmt.parseInt(u64, bytes, base)) |small_int| {
const src = token_starts[int_token];
const result = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.comptime_int),
.val = try Value.Tag.int_u64.create(arena, small_int),
});
if (std.fmt.parseInt(u64, prefixed_bytes, 0)) |small_int| {
const result: zir.Inst.Index = switch (small_int) {
0 => @enumToInt(zir.Const.zero),
1 => @enumToInt(zir.Const.one),
else => try addZirInt(small_int),
};
return rvalue(mod, scope, rl, result);
} else |err| {
return mod.failTok(scope, int_token, "TODO implement int literals that don't fit in a u64", .{});
@ -3316,7 +3109,7 @@ fn asRlPtr(
// Detect whether this expr() call goes into rvalue() to store the result into the
// result location. If it does, elide the coerce_result_ptr instruction
// as well as the store instruction, instead passing the result as an rvalue.
var as_scope: Scope.GenZIR = .{
var as_scope: Scope.GenZir = .{
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
@ -3327,7 +3120,7 @@ fn asRlPtr(
as_scope.rl_ptr = try addZIRBinOp(mod, &as_scope.base, src, .coerce_result_ptr, dest_type, result_ptr);
const result = try expr(mod, &as_scope.base, .{ .block_ptr = &as_scope }, operand_node);
const parent_zir = &scope.getGenZIR().instructions;
const parent_zir = &scope.getGenZir().instructions;
if (as_scope.rvalue_rl_count == 1) {
// Busted! This expression didn't actually need a pointer.
const expected_len = parent_zir.items.len + as_scope.instructions.items.len - 2;
@ -3622,39 +3415,47 @@ fn callExpr(
mod: *Module,
scope: *Scope,
rl: ResultLoc,
node: ast.Node.Index,
call: ast.full.Call,
) InnerError!*zir.Inst {
if (call.async_token) |async_token| {
return mod.failTok(scope, async_token, "TODO implement async fn call", .{});
}
const tree = scope.tree();
const main_tokens = tree.nodes.items(.main_token);
const token_starts = tree.tokens.items(.start);
const lhs = try expr(mod, scope, .none, call.ast.fn_expr);
const args = try scope.getGenZIR().arena.alloc(*zir.Inst, call.ast.params.len);
const args = try mod.gpa.alloc(zir.Inst.Index, call.ast.params.len);
defer mod.gpa.free(args);
const gen_zir = scope.getGenZir();
for (call.ast.params) |param_node, i| {
const param_src = token_starts[tree.firstToken(param_node)];
const param_type = try addZIRInst(mod, scope, param_src, zir.Inst.ParamType, .{
.func = lhs,
.arg_index = i,
}, .{});
const param_type = try gen_zir.addParamType(.{
.callee = lhs,
.param_index = i,
});
args[i] = try expr(mod, scope, .{ .ty = param_type }, param_node);
}
const src = token_starts[call.ast.lparen];
var modifier: std.builtin.CallOptions.Modifier = .auto;
if (call.async_token) |_| modifier = .async_kw;
const result = try addZIRInst(mod, scope, src, zir.Inst.Call, .{
.func = lhs,
.args = args,
.modifier = modifier,
}, .{});
// TODO function call with result location
return rvalue(mod, scope, rl, result);
const modifier: std.builtin.CallOptions.Modifier = switch (call.async_token != null) {
true => .async_kw,
false => .auto,
};
const result: zir.Inst.Index = res: {
const tag: zir.Inst.Tag = switch (modifier) {
.auto => switch (args.len == 0) {
true => break :res try gen_zir.addCallNone(lhs, node),
false => .call,
},
.async_kw => .call_async_kw,
.never_tail => unreachable,
.never_inline => unreachable,
.no_async => .call_no_async,
.always_tail => unreachable,
.always_inline => unreachable,
.compile_time => .call_compile_time,
};
break :res try gen_zir.addCall(tag, lhs, args, node);
};
return rvalue(mod, scope, rl, result); // TODO function call with result location
}
fn suspendExpr(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!*zir.Inst {
@ -3748,11 +3549,17 @@ fn resumeExpr(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!*zir
return addZIRUnOp(mod, scope, src, .@"resume", operand);
}
pub const simple_types = std.ComptimeStringMap(Value.Tag, .{
pub const simple_types = std.ComptimeStringMap(zir.Const, .{
.{ "u8", .u8_type },
.{ "i8", .i8_type },
.{ "isize", .isize_type },
.{ "u16", .u16_type },
.{ "i16", .i16_type },
.{ "u32", .u32_type },
.{ "i32", .i32_type },
.{ "u64", .u64_type },
.{ "i64", .i64_type },
.{ "usize", .usize_type },
.{ "isize", .isize_type },
.{ "c_short", .c_short_type },
.{ "c_ushort", .c_ushort_type },
.{ "c_int", .c_int_type },
@ -3774,6 +3581,13 @@ pub const simple_types = std.ComptimeStringMap(Value.Tag, .{
.{ "comptime_int", .comptime_int_type },
.{ "comptime_float", .comptime_float_type },
.{ "noreturn", .noreturn_type },
.{ "null", .null_type },
.{ "undefined", .undefined_type },
.{ "anyframe", .anyframe_type },
.{ "undefined", .undef },
.{ "null", .null_value },
.{ "true", .bool_true },
.{ "false", .bool_false },
});
fn nodeMayNeedMemoryLocation(scope: *Scope, start_node: ast.Node.Index) bool {
@ -4045,7 +3859,7 @@ fn rvalueVoid(
return rvalue(mod, scope, rl, void_inst);
}
fn rlStrategy(rl: ResultLoc, block_scope: *Scope.GenZIR) ResultLoc.Strategy {
fn rlStrategy(rl: ResultLoc, block_scope: *Scope.GenZir) ResultLoc.Strategy {
var elide_store_to_block_ptr_instructions = false;
switch (rl) {
// In this branch there will not be any store_to_block_ptr instructions.
@ -4099,7 +3913,7 @@ fn makeOptionalTypeResultLoc(mod: *Module, scope: *Scope, src: usize, rl: Result
}
}
fn setBlockResultLoc(block_scope: *Scope.GenZIR, parent_rl: ResultLoc) void {
fn setBlockResultLoc(block_scope: *Scope.GenZir, parent_rl: ResultLoc) void {
// Depending on whether the result location is a pointer or value, different
// ZIR needs to be generated. In the former case we rely on storing to the
// pointer to communicate the result, and use breakvoid; in the latter case
@ -4137,7 +3951,7 @@ pub fn addZirInstTag(
comptime tag: zir.Inst.Tag,
positionals: std.meta.fieldInfo(tag.Type(), .positionals).field_type,
) !*zir.Inst {
const gen_zir = scope.getGenZIR();
const gen_zir = scope.getGenZir();
try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
const inst = try gen_zir.arena.create(tag.Type());
inst.* = .{
@ -4160,7 +3974,7 @@ pub fn addZirInstT(
tag: zir.Inst.Tag,
positionals: std.meta.fieldInfo(T, .positionals).field_type,
) !*T {
const gen_zir = scope.getGenZIR();
const gen_zir = scope.getGenZir();
try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
const inst = try gen_zir.arena.create(T);
inst.* = .{
@ -4183,7 +3997,7 @@ pub fn addZIRInstSpecial(
positionals: std.meta.fieldInfo(T, .positionals).field_type,
kw_args: std.meta.fieldInfo(T, .kw_args).field_type,
) !*T {
const gen_zir = scope.getGenZIR();
const gen_zir = scope.getGenZir();
try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
const inst = try gen_zir.arena.create(T);
inst.* = .{
@ -4199,7 +4013,7 @@ pub fn addZIRInstSpecial(
}
pub fn addZIRNoOpT(mod: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst.NoOp {
const gen_zir = scope.getGenZIR();
const gen_zir = scope.getGenZir();
try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
const inst = try gen_zir.arena.create(zir.Inst.NoOp);
inst.* = .{
@ -4226,7 +4040,7 @@ pub fn addZIRUnOp(
tag: zir.Inst.Tag,
operand: *zir.Inst,
) !*zir.Inst {
const gen_zir = scope.getGenZIR();
const gen_zir = scope.getGenZir();
try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
const inst = try gen_zir.arena.create(zir.Inst.UnOp);
inst.* = .{
@ -4251,7 +4065,7 @@ pub fn addZIRBinOp(
lhs: *zir.Inst,
rhs: *zir.Inst,
) !*zir.Inst {
const gen_zir = scope.getGenZIR();
const gen_zir = scope.getGenZir();
try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
const inst = try gen_zir.arena.create(zir.Inst.BinOp);
inst.* = .{
@ -4276,7 +4090,7 @@ pub fn addZIRInstBlock(
tag: zir.Inst.Tag,
body: zir.Body,
) !*zir.Inst.Block {
const gen_zir = scope.getGenZIR();
const gen_zir = scope.getGenZir();
try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
const inst = try gen_zir.arena.create(zir.Inst.Block);
inst.* = .{

View File

@ -360,7 +360,8 @@ pub const Inst = struct {
base: Inst,
asm_source: []const u8,
is_volatile: bool,
output: ?[]const u8,
output: ?*Inst,
output_name: ?[]const u8,
inputs: []const []const u8,
clobbers: []const []const u8,
args: []const *Inst,
@ -589,3 +590,445 @@ pub const Inst = struct {
pub const Body = struct {
instructions: []*Inst,
};
/// For debugging purposes, prints a function representation to stderr.
pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void {
const allocator = old_module.gpa;
var ctx: DumpTzir = .{
.allocator = allocator,
.arena = std.heap.ArenaAllocator.init(allocator),
.old_module = &old_module,
.module_fn = module_fn,
.indent = 2,
.inst_table = DumpTzir.InstTable.init(allocator),
.partial_inst_table = DumpTzir.InstTable.init(allocator),
.const_table = DumpTzir.InstTable.init(allocator),
};
defer ctx.inst_table.deinit();
defer ctx.partial_inst_table.deinit();
defer ctx.const_table.deinit();
defer ctx.arena.deinit();
switch (module_fn.state) {
.queued => std.debug.print("(queued)", .{}),
.inline_only => std.debug.print("(inline_only)", .{}),
.in_progress => std.debug.print("(in_progress)", .{}),
.sema_failure => std.debug.print("(sema_failure)", .{}),
.dependency_failure => std.debug.print("(dependency_failure)", .{}),
.success => {
const writer = std.io.getStdErr().writer();
ctx.dump(module_fn.body, writer) catch @panic("failed to dump TZIR");
},
}
}
const DumpTzir = struct {
allocator: *Allocator,
arena: std.heap.ArenaAllocator,
old_module: *const IrModule,
module_fn: *IrModule.Fn,
indent: usize,
inst_table: InstTable,
partial_inst_table: InstTable,
const_table: InstTable,
next_index: usize = 0,
next_partial_index: usize = 0,
next_const_index: usize = 0,
const InstTable = std.AutoArrayHashMap(*ir.Inst, usize);
/// TODO: Improve this code to include a stack of ir.Body and store the instructions
/// in there. Now we are putting all the instructions in a function local table,
/// however instructions that are in a Body can be thown away when the Body ends.
fn dump(dtz: *DumpTzir, body: ir.Body, writer: std.fs.File.Writer) !void {
// First pass to pre-populate the table so that we can show even invalid references.
// Must iterate the same order we iterate the second time.
// We also look for constants and put them in the const_table.
try dtz.fetchInstsAndResolveConsts(body);
std.debug.print("Module.Function(name={s}):\n", .{dtz.module_fn.owner_decl.name});
for (dtz.const_table.items()) |entry| {
const constant = entry.key.castTag(.constant).?;
try writer.print(" @{d}: {} = {};\n", .{
entry.value, constant.base.ty, constant.val,
});
}
return dtz.dumpBody(body, writer);
}
fn fetchInstsAndResolveConsts(dtz: *DumpTzir, body: ir.Body) error{OutOfMemory}!void {
for (body.instructions) |inst| {
try dtz.inst_table.put(inst, dtz.next_index);
dtz.next_index += 1;
switch (inst.tag) {
.alloc,
.retvoid,
.unreach,
.breakpoint,
.dbg_stmt,
.arg,
=> {},
.ref,
.ret,
.bitcast,
.not,
.is_non_null,
.is_non_null_ptr,
.is_null,
.is_null_ptr,
.is_err,
.is_err_ptr,
.ptrtoint,
.floatcast,
.intcast,
.load,
.optional_payload,
.optional_payload_ptr,
.wrap_optional,
.wrap_errunion_payload,
.wrap_errunion_err,
.unwrap_errunion_payload,
.unwrap_errunion_err,
.unwrap_errunion_payload_ptr,
.unwrap_errunion_err_ptr,
=> {
const un_op = inst.cast(ir.Inst.UnOp).?;
try dtz.findConst(un_op.operand);
},
.add,
.sub,
.mul,
.cmp_lt,
.cmp_lte,
.cmp_eq,
.cmp_gte,
.cmp_gt,
.cmp_neq,
.store,
.bool_and,
.bool_or,
.bit_and,
.bit_or,
.xor,
=> {
const bin_op = inst.cast(ir.Inst.BinOp).?;
try dtz.findConst(bin_op.lhs);
try dtz.findConst(bin_op.rhs);
},
.br => {
const br = inst.castTag(.br).?;
try dtz.findConst(&br.block.base);
try dtz.findConst(br.operand);
},
.br_block_flat => {
const br_block_flat = inst.castTag(.br_block_flat).?;
try dtz.findConst(&br_block_flat.block.base);
try dtz.fetchInstsAndResolveConsts(br_block_flat.body);
},
.br_void => {
const br_void = inst.castTag(.br_void).?;
try dtz.findConst(&br_void.block.base);
},
.block => {
const block = inst.castTag(.block).?;
try dtz.fetchInstsAndResolveConsts(block.body);
},
.condbr => {
const condbr = inst.castTag(.condbr).?;
try dtz.findConst(condbr.condition);
try dtz.fetchInstsAndResolveConsts(condbr.then_body);
try dtz.fetchInstsAndResolveConsts(condbr.else_body);
},
.loop => {
const loop = inst.castTag(.loop).?;
try dtz.fetchInstsAndResolveConsts(loop.body);
},
.call => {
const call = inst.castTag(.call).?;
try dtz.findConst(call.func);
for (call.args) |arg| {
try dtz.findConst(arg);
}
},
// TODO fill out this debug printing
.assembly,
.constant,
.varptr,
.switchbr,
=> {},
}
}
}
fn dumpBody(dtz: *DumpTzir, body: ir.Body, writer: std.fs.File.Writer) (std.fs.File.WriteError || error{OutOfMemory})!void {
for (body.instructions) |inst| {
const my_index = dtz.next_partial_index;
try dtz.partial_inst_table.put(inst, my_index);
dtz.next_partial_index += 1;
try writer.writeByteNTimes(' ', dtz.indent);
try writer.print("%{d}: {} = {s}(", .{
my_index, inst.ty, @tagName(inst.tag),
});
switch (inst.tag) {
.alloc,
.retvoid,
.unreach,
.breakpoint,
.dbg_stmt,
=> try writer.writeAll(")\n"),
.ref,
.ret,
.bitcast,
.not,
.is_non_null,
.is_null,
.is_non_null_ptr,
.is_null_ptr,
.is_err,
.is_err_ptr,
.ptrtoint,
.floatcast,
.intcast,
.load,
.optional_payload,
.optional_payload_ptr,
.wrap_optional,
.wrap_errunion_err,
.wrap_errunion_payload,
.unwrap_errunion_err,
.unwrap_errunion_payload,
.unwrap_errunion_payload_ptr,
.unwrap_errunion_err_ptr,
=> {
const un_op = inst.cast(ir.Inst.UnOp).?;
const kinky = try dtz.writeInst(writer, un_op.operand);
if (kinky != null) {
try writer.writeAll(") // Instruction does not dominate all uses!\n");
} else {
try writer.writeAll(")\n");
}
},
.add,
.sub,
.mul,
.cmp_lt,
.cmp_lte,
.cmp_eq,
.cmp_gte,
.cmp_gt,
.cmp_neq,
.store,
.bool_and,
.bool_or,
.bit_and,
.bit_or,
.xor,
=> {
const bin_op = inst.cast(ir.Inst.BinOp).?;
const lhs_kinky = try dtz.writeInst(writer, bin_op.lhs);
try writer.writeAll(", ");
const rhs_kinky = try dtz.writeInst(writer, bin_op.rhs);
if (lhs_kinky != null or rhs_kinky != null) {
try writer.writeAll(") // Instruction does not dominate all uses!");
if (lhs_kinky) |lhs| {
try writer.print(" %{d}", .{lhs});
}
if (rhs_kinky) |rhs| {
try writer.print(" %{d}", .{rhs});
}
try writer.writeAll("\n");
} else {
try writer.writeAll(")\n");
}
},
.arg => {
const arg = inst.castTag(.arg).?;
try writer.print("{s})\n", .{arg.name});
},
.br => {
const br = inst.castTag(.br).?;
const lhs_kinky = try dtz.writeInst(writer, &br.block.base);
try writer.writeAll(", ");
const rhs_kinky = try dtz.writeInst(writer, br.operand);
if (lhs_kinky != null or rhs_kinky != null) {
try writer.writeAll(") // Instruction does not dominate all uses!");
if (lhs_kinky) |lhs| {
try writer.print(" %{d}", .{lhs});
}
if (rhs_kinky) |rhs| {
try writer.print(" %{d}", .{rhs});
}
try writer.writeAll("\n");
} else {
try writer.writeAll(")\n");
}
},
.br_block_flat => {
const br_block_flat = inst.castTag(.br_block_flat).?;
const block_kinky = try dtz.writeInst(writer, &br_block_flat.block.base);
if (block_kinky != null) {
try writer.writeAll(", { // Instruction does not dominate all uses!\n");
} else {
try writer.writeAll(", {\n");
}
const old_indent = dtz.indent;
dtz.indent += 2;
try dtz.dumpBody(br_block_flat.body, writer);
dtz.indent = old_indent;
try writer.writeByteNTimes(' ', dtz.indent);
try writer.writeAll("})\n");
},
.br_void => {
const br_void = inst.castTag(.br_void).?;
const kinky = try dtz.writeInst(writer, &br_void.block.base);
if (kinky) |_| {
try writer.writeAll(") // Instruction does not dominate all uses!\n");
} else {
try writer.writeAll(")\n");
}
},
.block => {
const block = inst.castTag(.block).?;
try writer.writeAll("{\n");
const old_indent = dtz.indent;
dtz.indent += 2;
try dtz.dumpBody(block.body, writer);
dtz.indent = old_indent;
try writer.writeByteNTimes(' ', dtz.indent);
try writer.writeAll("})\n");
},
.condbr => {
const condbr = inst.castTag(.condbr).?;
const condition_kinky = try dtz.writeInst(writer, condbr.condition);
if (condition_kinky != null) {
try writer.writeAll(", { // Instruction does not dominate all uses!\n");
} else {
try writer.writeAll(", {\n");
}
const old_indent = dtz.indent;
dtz.indent += 2;
try dtz.dumpBody(condbr.then_body, writer);
try writer.writeByteNTimes(' ', old_indent);
try writer.writeAll("}, {\n");
try dtz.dumpBody(condbr.else_body, writer);
dtz.indent = old_indent;
try writer.writeByteNTimes(' ', old_indent);
try writer.writeAll("})\n");
},
.loop => {
const loop = inst.castTag(.loop).?;
try writer.writeAll("{\n");
const old_indent = dtz.indent;
dtz.indent += 2;
try dtz.dumpBody(loop.body, writer);
dtz.indent = old_indent;
try writer.writeByteNTimes(' ', dtz.indent);
try writer.writeAll("})\n");
},
.call => {
const call = inst.castTag(.call).?;
const args_kinky = try dtz.allocator.alloc(?usize, call.args.len);
defer dtz.allocator.free(args_kinky);
std.mem.set(?usize, args_kinky, null);
var any_kinky_args = false;
const func_kinky = try dtz.writeInst(writer, call.func);
for (call.args) |arg, i| {
try writer.writeAll(", ");
args_kinky[i] = try dtz.writeInst(writer, arg);
any_kinky_args = any_kinky_args or args_kinky[i] != null;
}
if (func_kinky != null or any_kinky_args) {
try writer.writeAll(") // Instruction does not dominate all uses!");
if (func_kinky) |func_index| {
try writer.print(" %{d}", .{func_index});
}
for (args_kinky) |arg_kinky| {
if (arg_kinky) |arg_index| {
try writer.print(" %{d}", .{arg_index});
}
}
try writer.writeAll("\n");
} else {
try writer.writeAll(")\n");
}
},
// TODO fill out this debug printing
.assembly,
.constant,
.varptr,
.switchbr,
=> {
try writer.writeAll("!TODO!)\n");
},
}
}
}
fn writeInst(dtz: *DumpTzir, writer: std.fs.File.Writer, inst: *ir.Inst) !?usize {
if (dtz.partial_inst_table.get(inst)) |operand_index| {
try writer.print("%{d}", .{operand_index});
return null;
} else if (dtz.const_table.get(inst)) |operand_index| {
try writer.print("@{d}", .{operand_index});
return null;
} else if (dtz.inst_table.get(inst)) |operand_index| {
try writer.print("%{d}", .{operand_index});
return operand_index;
} else {
try writer.writeAll("!BADREF!");
return null;
}
}
fn findConst(dtz: *DumpTzir, operand: *ir.Inst) !void {
if (operand.tag == .constant) {
try dtz.const_table.put(operand, dtz.next_const_index);
dtz.next_const_index += 1;
}
}
};

View File

@ -863,7 +863,10 @@ pub const Type = extern union {
}
pub fn isNoReturn(self: Type) bool {
return self.zigTypeTag() == .NoReturn;
const definitely_correct_result = self.zigTypeTag() == .NoReturn;
const fast_result = self.tag_if_small_enough == Tag.noreturn;
assert(fast_result == definitely_correct_result);
return fast_result;
}
/// Asserts that hasCodeGenBits() is true.
@ -3464,18 +3467,20 @@ pub const Type = extern union {
.int_unsigned,
=> Payload.Bits,
.error_set,
.@"enum",
.@"struct",
.@"union",
=> Payload.Decl,
.array => Payload.Array,
.array_sentinel => Payload.ArraySentinel,
.pointer => Payload.Pointer,
.function => Payload.Function,
.error_union => Payload.ErrorUnion,
.error_set => Payload.Decl,
.error_set_single => Payload.Name,
.empty_struct => Payload.ContainerScope,
.@"enum" => Payload.Enum,
.@"struct" => Payload.Struct,
.@"union" => Payload.Union,
.@"opaque" => Payload.Opaque,
.empty_struct => Payload.ContainerScope,
};
}
@ -3598,13 +3603,8 @@ pub const Type = extern union {
pub const Opaque = struct {
base: Payload = .{ .tag = .@"opaque" },
scope: Module.Scope.Container,
data: Module.Scope.Container,
};
pub const Enum = @import("type/Enum.zig");
pub const Struct = @import("type/Struct.zig");
pub const Union = @import("type/Union.zig");
};
};

View File

@ -1,55 +0,0 @@
const std = @import("std");
const zir = @import("../zir.zig");
const Value = @import("../value.zig").Value;
const Type = @import("../type.zig").Type;
const Module = @import("../Module.zig");
const Scope = Module.Scope;
const Enum = @This();
base: Type.Payload = .{ .tag = .@"enum" },
analysis: union(enum) {
queued: Zir,
in_progress,
resolved: Size,
failed,
},
scope: Scope.Container,
pub const Field = struct {
value: Value,
};
pub const Zir = struct {
body: zir.Body,
inst: *zir.Inst,
};
pub const Size = struct {
tag_type: Type,
fields: std.StringArrayHashMapUnmanaged(Field),
};
pub fn resolve(self: *Enum, mod: *Module, scope: *Scope) !void {
const zir = switch (self.analysis) {
.failed => return error.AnalysisFail,
.resolved => return,
.in_progress => {
return mod.fail(scope, src, "enum '{}' depends on itself", .{enum_name});
},
.queued => |zir| zir,
};
self.analysis = .in_progress;
// TODO
}
// TODO should this resolve the type or assert that it has already been resolved?
pub fn abiAlignment(self: *Enum, target: std.Target) u32 {
switch (self.analysis) {
.queued => unreachable, // alignment has not been resolved
.in_progress => unreachable, // alignment has not been resolved
.failed => unreachable, // type resolution failed
.resolved => |r| return r.tag_type.abiAlignment(target),
}
}

View File

@ -1,56 +0,0 @@
const std = @import("std");
const zir = @import("../zir.zig");
const Value = @import("../value.zig").Value;
const Type = @import("../type.zig").Type;
const Module = @import("../Module.zig");
const Scope = Module.Scope;
const Struct = @This();
base: Type.Payload = .{ .tag = .@"struct" },
analysis: union(enum) {
queued: Zir,
zero_bits_in_progress,
zero_bits: Zero,
in_progress,
// alignment: Align,
resolved: Size,
failed,
},
scope: Scope.Container,
pub const Field = struct {
value: Value,
};
pub const Zir = struct {
body: zir.Body,
inst: *zir.Inst,
};
pub const Zero = struct {
is_zero_bits: bool,
fields: std.StringArrayHashMapUnmanaged(Field),
};
pub const Size = struct {
is_zero_bits: bool,
alignment: u32,
size: u32,
fields: std.StringArrayHashMapUnmanaged(Field),
};
pub fn resolveZeroBits(self: *Struct, mod: *Module, scope: *Scope) !void {
const zir = switch (self.analysis) {
.failed => return error.AnalysisFail,
.zero_bits_in_progress => {
return mod.fail(scope, src, "struct '{}' depends on itself", .{});
},
.queued => |zir| zir,
else => return,
};
self.analysis = .zero_bits_in_progress;
// TODO
}

View File

@ -1,56 +0,0 @@
const std = @import("std");
const zir = @import("../zir.zig");
const Value = @import("../value.zig").Value;
const Type = @import("../type.zig").Type;
const Module = @import("../Module.zig");
const Scope = Module.Scope;
const Union = @This();
base: Type.Payload = .{ .tag = .@"struct" },
analysis: union(enum) {
queued: Zir,
zero_bits_in_progress,
zero_bits: Zero,
in_progress,
// alignment: Align,
resolved: Size,
failed,
},
scope: Scope.Container,
pub const Field = struct {
value: Value,
};
pub const Zir = struct {
body: zir.Body,
inst: *zir.Inst,
};
pub const Zero = struct {
is_zero_bits: bool,
fields: std.StringArrayHashMapUnmanaged(Field),
};
pub const Size = struct {
is_zero_bits: bool,
alignment: u32,
size: u32,
fields: std.StringArrayHashMapUnmanaged(Field),
};
pub fn resolveZeroBits(self: *Union, mod: *Module, scope: *Scope) !void {
const zir = switch (self.analysis) {
.failed => return error.AnalysisFail,
.zero_bits_in_progress => {
return mod.fail(scope, src, "union '{}' depends on itself", .{});
},
.queued => |zir| zir,
else => return,
};
self.analysis = .zero_bits_in_progress;
// TODO
}

View File

@ -69,11 +69,12 @@ pub const Value = extern union {
one,
void_value,
unreachable_value,
empty_struct_value,
empty_array,
null_value,
bool_true,
bool_false, // See last_no_payload_tag below.
bool_false,
empty_struct_value,
empty_array, // See last_no_payload_tag below.
// After this, the tag requires a payload.
ty,
@ -107,7 +108,7 @@ pub const Value = extern union {
/// to an inferred allocation. It does not support any of the normal value queries.
inferred_alloc,
pub const last_no_payload_tag = Tag.bool_false;
pub const last_no_payload_tag = Tag.empty_array;
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
pub fn Type(comptime t: Tag) type {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff