Merge pull request #5868 from ziglang/stage2-improvements

Stage2 improvements
This commit is contained in:
Andrew Kelley
2020-07-14 09:24:43 +00:00
committed by GitHub
12 changed files with 1024 additions and 478 deletions

View File

@@ -410,7 +410,20 @@ pub const Node = struct {
// Operators
InfixOp,
PrefixOp,
AddressOf,
Await,
BitNot,
BoolNot,
OptionalType,
Negation,
NegationWrap,
Resume,
Try,
ArrayType,
/// ArrayType but has a sentinel node.
ArrayTypeSentinel,
PtrType,
SliceType,
/// Not all suffix operations are under this tag. To save memory, some
/// suffix operations have dedicated Node tags.
SuffixOp,
@@ -1797,85 +1810,116 @@ pub const Node = struct {
}
};
pub const PrefixOp = struct {
base: Node = Node{ .id = .PrefixOp },
pub const AddressOf = SimplePrefixOp(.AddressOf);
pub const Await = SimplePrefixOp(.Await);
pub const BitNot = SimplePrefixOp(.BitNot);
pub const BoolNot = SimplePrefixOp(.BoolNot);
pub const OptionalType = SimplePrefixOp(.OptionalType);
pub const Negation = SimplePrefixOp(.Negation);
pub const NegationWrap = SimplePrefixOp(.NegationWrap);
pub const Resume = SimplePrefixOp(.Resume);
pub const Try = SimplePrefixOp(.Try);
pub fn SimplePrefixOp(comptime tag: Id) type {
return struct {
base: Node = Node{ .id = tag },
op_token: TokenIndex,
rhs: *Node,
const Self = @This();
pub fn iterate(self: *const Self, index: usize) ?*Node {
if (index == 0) return self.rhs;
return null;
}
pub fn firstToken(self: *const Self) TokenIndex {
return self.op_token;
}
pub fn lastToken(self: *const Self) TokenIndex {
return self.rhs.lastToken();
}
};
}
pub const ArrayType = struct {
base: Node = Node{ .id = .ArrayType },
op_token: TokenIndex,
op: Op,
rhs: *Node,
len_expr: *Node,
pub const Op = union(enum) {
AddressOf,
ArrayType: ArrayInfo,
Await,
BitNot,
BoolNot,
OptionalType,
Negation,
NegationWrap,
Resume,
PtrType: PtrInfo,
SliceType: PtrInfo,
Try,
};
pub const ArrayInfo = struct {
len_expr: *Node,
sentinel: ?*Node,
};
pub const PtrInfo = struct {
allowzero_token: ?TokenIndex = null,
align_info: ?Align = null,
const_token: ?TokenIndex = null,
volatile_token: ?TokenIndex = null,
sentinel: ?*Node = null,
pub const Align = struct {
node: *Node,
bit_range: ?BitRange,
pub const BitRange = struct {
start: *Node,
end: *Node,
};
};
};
pub fn iterate(self: *const PrefixOp, index: usize) ?*Node {
pub fn iterate(self: *const ArrayType, index: usize) ?*Node {
var i = index;
switch (self.op) {
.PtrType, .SliceType => |addr_of_info| {
if (addr_of_info.sentinel) |sentinel| {
if (i < 1) return sentinel;
i -= 1;
}
if (i < 1) return self.len_expr;
i -= 1;
if (addr_of_info.align_info) |align_info| {
if (i < 1) return align_info.node;
i -= 1;
}
},
if (i < 1) return self.rhs;
i -= 1;
.ArrayType => |array_info| {
if (i < 1) return array_info.len_expr;
i -= 1;
if (array_info.sentinel) |sentinel| {
if (i < 1) return sentinel;
i -= 1;
}
},
return null;
}
.AddressOf,
.Await,
.BitNot,
.BoolNot,
.OptionalType,
.Negation,
.NegationWrap,
.Try,
.Resume,
=> {},
pub fn firstToken(self: *const ArrayType) TokenIndex {
return self.op_token;
}
pub fn lastToken(self: *const ArrayType) TokenIndex {
return self.rhs.lastToken();
}
};
pub const ArrayTypeSentinel = struct {
base: Node = Node{ .id = .ArrayTypeSentinel },
op_token: TokenIndex,
rhs: *Node,
len_expr: *Node,
sentinel: *Node,
pub fn iterate(self: *const ArrayTypeSentinel, index: usize) ?*Node {
var i = index;
if (i < 1) return self.len_expr;
i -= 1;
if (i < 1) return self.sentinel;
i -= 1;
if (i < 1) return self.rhs;
i -= 1;
return null;
}
pub fn firstToken(self: *const ArrayTypeSentinel) TokenIndex {
return self.op_token;
}
pub fn lastToken(self: *const ArrayTypeSentinel) TokenIndex {
return self.rhs.lastToken();
}
};
pub const PtrType = struct {
base: Node = Node{ .id = .PtrType },
op_token: TokenIndex,
rhs: *Node,
/// TODO Add a u8 flags field to Node where it would otherwise be padding, and each bit represents
/// one of these possibly-null things. Then we have them directly follow the PtrType in memory.
ptr_info: PtrInfo = .{},
pub fn iterate(self: *const PtrType, index: usize) ?*Node {
var i = index;
if (self.ptr_info.sentinel) |sentinel| {
if (i < 1) return sentinel;
i -= 1;
}
if (self.ptr_info.align_info) |align_info| {
if (i < 1) return align_info.node;
i -= 1;
}
if (i < 1) return self.rhs;
@@ -1884,11 +1928,47 @@ pub const Node = struct {
return null;
}
pub fn firstToken(self: *const PrefixOp) TokenIndex {
pub fn firstToken(self: *const PtrType) TokenIndex {
return self.op_token;
}
pub fn lastToken(self: *const PrefixOp) TokenIndex {
pub fn lastToken(self: *const PtrType) TokenIndex {
return self.rhs.lastToken();
}
};
pub const SliceType = struct {
base: Node = Node{ .id = .SliceType },
op_token: TokenIndex,
rhs: *Node,
/// TODO Add a u8 flags field to Node where it would otherwise be padding, and each bit represents
/// one of these possibly-null things. Then we have them directly follow the SliceType in memory.
ptr_info: PtrInfo = .{},
pub fn iterate(self: *const SliceType, index: usize) ?*Node {
var i = index;
if (self.ptr_info.sentinel) |sentinel| {
if (i < 1) return sentinel;
i -= 1;
}
if (self.ptr_info.align_info) |align_info| {
if (i < 1) return align_info.node;
i -= 1;
}
if (i < 1) return self.rhs;
i -= 1;
return null;
}
pub fn firstToken(self: *const SliceType) TokenIndex {
return self.op_token;
}
pub fn lastToken(self: *const SliceType) TokenIndex {
return self.rhs.lastToken();
}
};
@@ -2797,6 +2877,24 @@ pub const Node = struct {
};
};
pub const PtrInfo = struct {
allowzero_token: ?TokenIndex = null,
align_info: ?Align = null,
const_token: ?TokenIndex = null,
volatile_token: ?TokenIndex = null,
sentinel: ?*Node = null,
pub const Align = struct {
node: *Node,
bit_range: ?BitRange = null,
pub const BitRange = struct {
start: *Node,
end: *Node,
};
};
};
test "iterate" {
var root = Node.Root{
.base = Node{ .id = Node.Id.Root },

View File

@@ -1120,10 +1120,9 @@ const Parser = struct {
const expr_node = try p.expectNode(parseExpr, .{
.ExpectedExpr = .{ .token = p.tok_i },
});
const node = try p.arena.allocator.create(Node.PrefixOp);
const node = try p.arena.allocator.create(Node.Resume);
node.* = .{
.op_token = token,
.op = .Resume,
.rhs = expr_node,
};
return &node.base;
@@ -2413,24 +2412,25 @@ const Parser = struct {
/// / KEYWORD_await
fn parsePrefixOp(p: *Parser) !?*Node {
const token = p.nextToken();
const op: Node.PrefixOp.Op = switch (p.token_ids[token]) {
.Bang => .BoolNot,
.Minus => .Negation,
.Tilde => .BitNot,
.MinusPercent => .NegationWrap,
.Ampersand => .AddressOf,
.Keyword_try => .Try,
.Keyword_await => .Await,
switch (p.token_ids[token]) {
.Bang => return p.allocSimplePrefixOp(.BoolNot, token),
.Minus => return p.allocSimplePrefixOp(.Negation, token),
.Tilde => return p.allocSimplePrefixOp(.BitNot, token),
.MinusPercent => return p.allocSimplePrefixOp(.NegationWrap, token),
.Ampersand => return p.allocSimplePrefixOp(.AddressOf, token),
.Keyword_try => return p.allocSimplePrefixOp(.Try, token),
.Keyword_await => return p.allocSimplePrefixOp(.Await, token),
else => {
p.putBackToken(token);
return null;
},
};
}
}
const node = try p.arena.allocator.create(Node.PrefixOp);
fn allocSimplePrefixOp(p: *Parser, comptime tag: Node.Id, token: TokenIndex) !?*Node {
const node = try p.arena.allocator.create(Node.SimplePrefixOp(tag));
node.* = .{
.op_token = token,
.op = op,
.rhs = undefined, // set by caller
};
return &node.base;
@@ -2450,19 +2450,14 @@ const Parser = struct {
/// / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
fn parsePrefixTypeOp(p: *Parser) !?*Node {
if (p.eatToken(.QuestionMark)) |token| {
const node = try p.arena.allocator.create(Node.PrefixOp);
const node = try p.arena.allocator.create(Node.OptionalType);
node.* = .{
.op_token = token,
.op = .OptionalType,
.rhs = undefined, // set by caller
};
return &node.base;
}
// TODO: Returning a AnyFrameType instead of PrefixOp makes casting and setting .rhs or
// .return_type more difficult for the caller (see parsePrefixOpExpr helper).
// Consider making the AnyFrameType a member of PrefixOp and add a
// PrefixOp.AnyFrameType variant?
if (p.eatToken(.Keyword_anyframe)) |token| {
const arrow = p.eatToken(.Arrow) orelse {
p.putBackToken(token);
@@ -2482,11 +2477,15 @@ const Parser = struct {
if (try p.parsePtrTypeStart()) |node| {
// If the token encountered was **, there will be two nodes instead of one.
// The attributes should be applied to the rightmost operator.
const prefix_op = node.cast(Node.PrefixOp).?;
var ptr_info = if (p.token_ids[prefix_op.op_token] == .AsteriskAsterisk)
&prefix_op.rhs.cast(Node.PrefixOp).?.op.PtrType
var ptr_info = if (node.cast(Node.PtrType)) |ptr_type|
if (p.token_ids[ptr_type.op_token] == .AsteriskAsterisk)
&ptr_type.rhs.cast(Node.PtrType).?.ptr_info
else
&ptr_type.ptr_info
else if (node.cast(Node.SliceType)) |slice_type|
&slice_type.ptr_info
else
&prefix_op.op.PtrType;
unreachable;
while (true) {
if (p.eatToken(.Keyword_align)) |align_token| {
@@ -2505,7 +2504,7 @@ const Parser = struct {
.ExpectedIntegerLiteral = .{ .token = p.tok_i },
});
break :bit_range_value Node.PrefixOp.PtrInfo.Align.BitRange{
break :bit_range_value ast.PtrInfo.Align.BitRange{
.start = range_start,
.end = range_end,
};
@@ -2519,7 +2518,7 @@ const Parser = struct {
continue;
}
ptr_info.align_info = Node.PrefixOp.PtrInfo.Align{
ptr_info.align_info = ast.PtrInfo.Align{
.node = expr_node,
.bit_range = bit_range,
};
@@ -2563,58 +2562,54 @@ const Parser = struct {
}
if (try p.parseArrayTypeStart()) |node| {
switch (node.cast(Node.PrefixOp).?.op) {
.ArrayType => {},
.SliceType => |*slice_type| {
// Collect pointer qualifiers in any order, but disallow duplicates
while (true) {
if (try p.parseByteAlign()) |align_expr| {
if (slice_type.align_info != null) {
try p.errors.append(p.gpa, .{
.ExtraAlignQualifier = .{ .token = p.tok_i - 1 },
});
continue;
}
slice_type.align_info = Node.PrefixOp.PtrInfo.Align{
.node = align_expr,
.bit_range = null,
};
if (node.cast(Node.SliceType)) |slice_type| {
// Collect pointer qualifiers in any order, but disallow duplicates
while (true) {
if (try p.parseByteAlign()) |align_expr| {
if (slice_type.ptr_info.align_info != null) {
try p.errors.append(p.gpa, .{
.ExtraAlignQualifier = .{ .token = p.tok_i - 1 },
});
continue;
}
if (p.eatToken(.Keyword_const)) |const_token| {
if (slice_type.const_token != null) {
try p.errors.append(p.gpa, .{
.ExtraConstQualifier = .{ .token = p.tok_i - 1 },
});
continue;
}
slice_type.const_token = const_token;
continue;
}
if (p.eatToken(.Keyword_volatile)) |volatile_token| {
if (slice_type.volatile_token != null) {
try p.errors.append(p.gpa, .{
.ExtraVolatileQualifier = .{ .token = p.tok_i - 1 },
});
continue;
}
slice_type.volatile_token = volatile_token;
continue;
}
if (p.eatToken(.Keyword_allowzero)) |allowzero_token| {
if (slice_type.allowzero_token != null) {
try p.errors.append(p.gpa, .{
.ExtraAllowZeroQualifier = .{ .token = p.tok_i - 1 },
});
continue;
}
slice_type.allowzero_token = allowzero_token;
continue;
}
break;
slice_type.ptr_info.align_info = ast.PtrInfo.Align{
.node = align_expr,
.bit_range = null,
};
continue;
}
},
else => unreachable,
if (p.eatToken(.Keyword_const)) |const_token| {
if (slice_type.ptr_info.const_token != null) {
try p.errors.append(p.gpa, .{
.ExtraConstQualifier = .{ .token = p.tok_i - 1 },
});
continue;
}
slice_type.ptr_info.const_token = const_token;
continue;
}
if (p.eatToken(.Keyword_volatile)) |volatile_token| {
if (slice_type.ptr_info.volatile_token != null) {
try p.errors.append(p.gpa, .{
.ExtraVolatileQualifier = .{ .token = p.tok_i - 1 },
});
continue;
}
slice_type.ptr_info.volatile_token = volatile_token;
continue;
}
if (p.eatToken(.Keyword_allowzero)) |allowzero_token| {
if (slice_type.ptr_info.allowzero_token != null) {
try p.errors.append(p.gpa, .{
.ExtraAllowZeroQualifier = .{ .token = p.tok_i - 1 },
});
continue;
}
slice_type.ptr_info.allowzero_token = allowzero_token;
continue;
}
break;
}
}
return node;
}
@@ -2728,29 +2723,32 @@ const Parser = struct {
null;
const rbracket = try p.expectToken(.RBracket);
const op: Node.PrefixOp.Op = if (expr) |len_expr|
.{
.ArrayType = .{
if (expr) |len_expr| {
if (sentinel) |s| {
const node = try p.arena.allocator.create(Node.ArrayTypeSentinel);
node.* = .{
.op_token = lbracket,
.rhs = undefined, // set by caller
.len_expr = len_expr,
.sentinel = sentinel,
},
.sentinel = s,
};
return &node.base;
} else {
const node = try p.arena.allocator.create(Node.ArrayType);
node.* = .{
.op_token = lbracket,
.rhs = undefined, // set by caller
.len_expr = len_expr,
};
return &node.base;
}
else
.{
.SliceType = Node.PrefixOp.PtrInfo{
.allowzero_token = null,
.align_info = null,
.const_token = null,
.volatile_token = null,
.sentinel = sentinel,
},
};
}
const node = try p.arena.allocator.create(Node.PrefixOp);
const node = try p.arena.allocator.create(Node.SliceType);
node.* = .{
.op_token = lbracket,
.op = op,
.rhs = undefined, // set by caller
.ptr_info = .{ .sentinel = sentinel },
};
return &node.base;
}
@@ -2768,28 +2766,26 @@ const Parser = struct {
})
else
null;
const node = try p.arena.allocator.create(Node.PrefixOp);
const node = try p.arena.allocator.create(Node.PtrType);
node.* = .{
.op_token = asterisk,
.op = .{ .PtrType = .{ .sentinel = sentinel } },
.rhs = undefined, // set by caller
.ptr_info = .{ .sentinel = sentinel },
};
return &node.base;
}
if (p.eatToken(.AsteriskAsterisk)) |double_asterisk| {
const node = try p.arena.allocator.create(Node.PrefixOp);
const node = try p.arena.allocator.create(Node.PtrType);
node.* = .{
.op_token = double_asterisk,
.op = .{ .PtrType = .{} },
.rhs = undefined, // set by caller
};
// Special case for **, which is its own token
const child = try p.arena.allocator.create(Node.PrefixOp);
const child = try p.arena.allocator.create(Node.PtrType);
child.* = .{
.op_token = double_asterisk,
.op = .{ .PtrType = .{} },
.rhs = undefined, // set by caller
};
node.rhs = &child.base;
@@ -2808,10 +2804,9 @@ const Parser = struct {
p.putBackToken(ident);
} else {
_ = try p.expectToken(.RBracket);
const node = try p.arena.allocator.create(Node.PrefixOp);
const node = try p.arena.allocator.create(Node.PtrType);
node.* = .{
.op_token = lbracket,
.op = .{ .PtrType = .{} },
.rhs = undefined, // set by caller
};
return &node.base;
@@ -2824,11 +2819,11 @@ const Parser = struct {
else
null;
_ = try p.expectToken(.RBracket);
const node = try p.arena.allocator.create(Node.PrefixOp);
const node = try p.arena.allocator.create(Node.PtrType);
node.* = .{
.op_token = lbracket,
.op = .{ .PtrType = .{ .sentinel = sentinel } },
.rhs = undefined, // set by caller
.ptr_info = .{ .sentinel = sentinel },
};
return &node.base;
}
@@ -3146,10 +3141,9 @@ const Parser = struct {
fn parseTry(p: *Parser) !?*Node {
const token = p.eatToken(.Keyword_try) orelse return null;
const node = try p.arena.allocator.create(Node.PrefixOp);
const node = try p.arena.allocator.create(Node.Try);
node.* = .{
.op_token = token,
.op = .Try,
.rhs = undefined, // set by caller
};
return &node.base;
@@ -3228,15 +3222,87 @@ const Parser = struct {
var rightmost_op = first_op;
while (true) {
switch (rightmost_op.id) {
.PrefixOp => {
var prefix_op = rightmost_op.cast(Node.PrefixOp).?;
.AddressOf => {
if (try opParseFn(p)) |rhs| {
rightmost_op.cast(Node.AddressOf).?.rhs = rhs;
rightmost_op = rhs;
} else break;
},
.Await => {
if (try opParseFn(p)) |rhs| {
rightmost_op.cast(Node.Await).?.rhs = rhs;
rightmost_op = rhs;
} else break;
},
.BitNot => {
if (try opParseFn(p)) |rhs| {
rightmost_op.cast(Node.BitNot).?.rhs = rhs;
rightmost_op = rhs;
} else break;
},
.BoolNot => {
if (try opParseFn(p)) |rhs| {
rightmost_op.cast(Node.BoolNot).?.rhs = rhs;
rightmost_op = rhs;
} else break;
},
.OptionalType => {
if (try opParseFn(p)) |rhs| {
rightmost_op.cast(Node.OptionalType).?.rhs = rhs;
rightmost_op = rhs;
} else break;
},
.Negation => {
if (try opParseFn(p)) |rhs| {
rightmost_op.cast(Node.Negation).?.rhs = rhs;
rightmost_op = rhs;
} else break;
},
.NegationWrap => {
if (try opParseFn(p)) |rhs| {
rightmost_op.cast(Node.NegationWrap).?.rhs = rhs;
rightmost_op = rhs;
} else break;
},
.Resume => {
if (try opParseFn(p)) |rhs| {
rightmost_op.cast(Node.Resume).?.rhs = rhs;
rightmost_op = rhs;
} else break;
},
.Try => {
if (try opParseFn(p)) |rhs| {
rightmost_op.cast(Node.Try).?.rhs = rhs;
rightmost_op = rhs;
} else break;
},
.ArrayType => {
if (try opParseFn(p)) |rhs| {
rightmost_op.cast(Node.ArrayType).?.rhs = rhs;
rightmost_op = rhs;
} else break;
},
.ArrayTypeSentinel => {
if (try opParseFn(p)) |rhs| {
rightmost_op.cast(Node.ArrayTypeSentinel).?.rhs = rhs;
rightmost_op = rhs;
} else break;
},
.SliceType => {
if (try opParseFn(p)) |rhs| {
rightmost_op.cast(Node.SliceType).?.rhs = rhs;
rightmost_op = rhs;
} else break;
},
.PtrType => {
var ptr_type = rightmost_op.cast(Node.PtrType).?;
// If the token encountered was **, there will be two nodes
if (p.token_ids[prefix_op.op_token] == .AsteriskAsterisk) {
rightmost_op = prefix_op.rhs;
prefix_op = rightmost_op.cast(Node.PrefixOp).?;
if (p.token_ids[ptr_type.op_token] == .AsteriskAsterisk) {
rightmost_op = ptr_type.rhs;
ptr_type = rightmost_op.cast(Node.PtrType).?;
}
if (try opParseFn(p)) |rhs| {
prefix_op.rhs = rhs;
ptr_type.rhs = rhs;
rightmost_op = rhs;
} else break;
},
@@ -3253,8 +3319,80 @@ const Parser = struct {
// If any prefix op existed, a child node on the RHS is required
switch (rightmost_op.id) {
.PrefixOp => {
const prefix_op = rightmost_op.cast(Node.PrefixOp).?;
.AddressOf => {
const prefix_op = rightmost_op.cast(Node.AddressOf).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});
},
.Await => {
const prefix_op = rightmost_op.cast(Node.Await).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});
},
.BitNot => {
const prefix_op = rightmost_op.cast(Node.BitNot).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});
},
.BoolNot => {
const prefix_op = rightmost_op.cast(Node.BoolNot).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});
},
.OptionalType => {
const prefix_op = rightmost_op.cast(Node.OptionalType).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});
},
.Negation => {
const prefix_op = rightmost_op.cast(Node.Negation).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});
},
.NegationWrap => {
const prefix_op = rightmost_op.cast(Node.NegationWrap).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});
},
.Resume => {
const prefix_op = rightmost_op.cast(Node.Resume).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});
},
.Try => {
const prefix_op = rightmost_op.cast(Node.Try).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});
},
.ArrayType => {
const prefix_op = rightmost_op.cast(Node.ArrayType).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});
},
.ArrayTypeSentinel => {
const prefix_op = rightmost_op.cast(Node.ArrayTypeSentinel).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});
},
.PtrType => {
const prefix_op = rightmost_op.cast(Node.PtrType).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});
},
.SliceType => {
const prefix_op = rightmost_op.cast(Node.SliceType).?;
prefix_op.rhs = try p.expectNode(childParseFn, .{
.InvalidToken = .{ .token = p.tok_i },
});

View File

@@ -468,166 +468,192 @@ fn renderExpression(
return renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.rhs, space);
},
.PrefixOp => {
const prefix_op_node = @fieldParentPtr(ast.Node.PrefixOp, "base", base);
.BitNot => {
const bit_not = @fieldParentPtr(ast.Node.BitNot, "base", base);
try renderToken(tree, stream, bit_not.op_token, indent, start_col, Space.None);
return renderExpression(allocator, stream, tree, indent, start_col, bit_not.rhs, space);
},
.BoolNot => {
const bool_not = @fieldParentPtr(ast.Node.BoolNot, "base", base);
try renderToken(tree, stream, bool_not.op_token, indent, start_col, Space.None);
return renderExpression(allocator, stream, tree, indent, start_col, bool_not.rhs, space);
},
.Negation => {
const negation = @fieldParentPtr(ast.Node.Negation, "base", base);
try renderToken(tree, stream, negation.op_token, indent, start_col, Space.None);
return renderExpression(allocator, stream, tree, indent, start_col, negation.rhs, space);
},
.NegationWrap => {
const negation_wrap = @fieldParentPtr(ast.Node.NegationWrap, "base", base);
try renderToken(tree, stream, negation_wrap.op_token, indent, start_col, Space.None);
return renderExpression(allocator, stream, tree, indent, start_col, negation_wrap.rhs, space);
},
.OptionalType => {
const opt_type = @fieldParentPtr(ast.Node.OptionalType, "base", base);
try renderToken(tree, stream, opt_type.op_token, indent, start_col, Space.None);
return renderExpression(allocator, stream, tree, indent, start_col, opt_type.rhs, space);
},
.AddressOf => {
const addr_of = @fieldParentPtr(ast.Node.AddressOf, "base", base);
try renderToken(tree, stream, addr_of.op_token, indent, start_col, Space.None);
return renderExpression(allocator, stream, tree, indent, start_col, addr_of.rhs, space);
},
.Try => {
const try_node = @fieldParentPtr(ast.Node.Try, "base", base);
try renderToken(tree, stream, try_node.op_token, indent, start_col, Space.Space);
return renderExpression(allocator, stream, tree, indent, start_col, try_node.rhs, space);
},
.Resume => {
const resume_node = @fieldParentPtr(ast.Node.Resume, "base", base);
try renderToken(tree, stream, resume_node.op_token, indent, start_col, Space.Space);
return renderExpression(allocator, stream, tree, indent, start_col, resume_node.rhs, space);
},
.Await => {
const await_node = @fieldParentPtr(ast.Node.Await, "base", base);
try renderToken(tree, stream, await_node.op_token, indent, start_col, Space.Space);
return renderExpression(allocator, stream, tree, indent, start_col, await_node.rhs, space);
},
switch (prefix_op_node.op) {
.PtrType => |ptr_info| {
const op_tok_id = tree.token_ids[prefix_op_node.op_token];
switch (op_tok_id) {
.Asterisk, .AsteriskAsterisk => try stream.writeByte('*'),
.LBracket => if (tree.token_ids[prefix_op_node.op_token + 2] == .Identifier)
try stream.writeAll("[*c")
else
try stream.writeAll("[*"),
else => unreachable,
}
if (ptr_info.sentinel) |sentinel| {
const colon_token = tree.prevToken(sentinel.firstToken());
try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
const sentinel_space = switch (op_tok_id) {
.LBracket => Space.None,
else => Space.Space,
};
try renderExpression(allocator, stream, tree, indent, start_col, sentinel, sentinel_space);
}
switch (op_tok_id) {
.Asterisk, .AsteriskAsterisk => {},
.LBracket => try stream.writeByte(']'),
else => unreachable,
}
if (ptr_info.allowzero_token) |allowzero_token| {
try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero
}
if (ptr_info.align_info) |align_info| {
const lparen_token = tree.prevToken(align_info.node.firstToken());
const align_token = tree.prevToken(lparen_token);
.ArrayType => {
const array_type = @fieldParentPtr(ast.Node.ArrayType, "base", base);
return renderArrayType(
allocator,
stream,
tree,
indent,
start_col,
array_type.op_token,
array_type.rhs,
array_type.len_expr,
null,
space,
);
},
.ArrayTypeSentinel => {
const array_type = @fieldParentPtr(ast.Node.ArrayTypeSentinel, "base", base);
return renderArrayType(
allocator,
stream,
tree,
indent,
start_col,
array_type.op_token,
array_type.rhs,
array_type.len_expr,
array_type.sentinel,
space,
);
},
try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align
try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
.PtrType => {
const ptr_type = @fieldParentPtr(ast.Node.PtrType, "base", base);
const op_tok_id = tree.token_ids[ptr_type.op_token];
switch (op_tok_id) {
.Asterisk, .AsteriskAsterisk => try stream.writeByte('*'),
.LBracket => if (tree.token_ids[ptr_type.op_token + 2] == .Identifier)
try stream.writeAll("[*c")
else
try stream.writeAll("[*"),
else => unreachable,
}
if (ptr_type.ptr_info.sentinel) |sentinel| {
const colon_token = tree.prevToken(sentinel.firstToken());
try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
const sentinel_space = switch (op_tok_id) {
.LBracket => Space.None,
else => Space.Space,
};
try renderExpression(allocator, stream, tree, indent, start_col, sentinel, sentinel_space);
}
switch (op_tok_id) {
.Asterisk, .AsteriskAsterisk => {},
.LBracket => try stream.writeByte(']'),
else => unreachable,
}
if (ptr_type.ptr_info.allowzero_token) |allowzero_token| {
try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero
}
if (ptr_type.ptr_info.align_info) |align_info| {
const lparen_token = tree.prevToken(align_info.node.firstToken());
const align_token = tree.prevToken(lparen_token);
try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None);
try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align
try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
if (align_info.bit_range) |bit_range| {
const colon1 = tree.prevToken(bit_range.start.firstToken());
const colon2 = tree.prevToken(bit_range.end.firstToken());
try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None);
try renderToken(tree, stream, colon1, indent, start_col, Space.None); // :
try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None);
try renderToken(tree, stream, colon2, indent, start_col, Space.None); // :
try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None);
if (align_info.bit_range) |bit_range| {
const colon1 = tree.prevToken(bit_range.start.firstToken());
const colon2 = tree.prevToken(bit_range.end.firstToken());
const rparen_token = tree.nextToken(bit_range.end.lastToken());
try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
} else {
const rparen_token = tree.nextToken(align_info.node.lastToken());
try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
}
}
if (ptr_info.const_token) |const_token| {
try renderToken(tree, stream, const_token, indent, start_col, Space.Space); // const
}
if (ptr_info.volatile_token) |volatile_token| {
try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile
}
},
try renderToken(tree, stream, colon1, indent, start_col, Space.None); // :
try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None);
try renderToken(tree, stream, colon2, indent, start_col, Space.None); // :
try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None);
.SliceType => |ptr_info| {
try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.None); // [
if (ptr_info.sentinel) |sentinel| {
const colon_token = tree.prevToken(sentinel.firstToken());
try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
try renderToken(tree, stream, tree.nextToken(sentinel.lastToken()), indent, start_col, Space.None); // ]
} else {
try renderToken(tree, stream, tree.nextToken(prefix_op_node.op_token), indent, start_col, Space.None); // ]
}
const rparen_token = tree.nextToken(bit_range.end.lastToken());
try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
} else {
const rparen_token = tree.nextToken(align_info.node.lastToken());
try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
}
}
if (ptr_type.ptr_info.const_token) |const_token| {
try renderToken(tree, stream, const_token, indent, start_col, Space.Space); // const
}
if (ptr_type.ptr_info.volatile_token) |volatile_token| {
try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile
}
return renderExpression(allocator, stream, tree, indent, start_col, ptr_type.rhs, space);
},
if (ptr_info.allowzero_token) |allowzero_token| {
try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero
}
if (ptr_info.align_info) |align_info| {
const lparen_token = tree.prevToken(align_info.node.firstToken());
const align_token = tree.prevToken(lparen_token);
try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align
try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None);
if (align_info.bit_range) |bit_range| {
const colon1 = tree.prevToken(bit_range.start.firstToken());
const colon2 = tree.prevToken(bit_range.end.firstToken());
try renderToken(tree, stream, colon1, indent, start_col, Space.None); // :
try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None);
try renderToken(tree, stream, colon2, indent, start_col, Space.None); // :
try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None);
const rparen_token = tree.nextToken(bit_range.end.lastToken());
try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
} else {
const rparen_token = tree.nextToken(align_info.node.lastToken());
try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
}
}
if (ptr_info.const_token) |const_token| {
try renderToken(tree, stream, const_token, indent, start_col, Space.Space);
}
if (ptr_info.volatile_token) |volatile_token| {
try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space);
}
},
.ArrayType => |array_info| {
const lbracket = prefix_op_node.op_token;
const rbracket = tree.nextToken(if (array_info.sentinel) |sentinel|
sentinel.lastToken()
else
array_info.len_expr.lastToken());
try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment;
const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment;
const new_indent = if (ends_with_comment) indent + indent_delta else indent;
const new_space = if (ends_with_comment) Space.Newline else Space.None;
try renderExpression(allocator, stream, tree, new_indent, start_col, array_info.len_expr, new_space);
if (starts_with_comment) {
try stream.writeByte('\n');
}
if (ends_with_comment or starts_with_comment) {
try stream.writeByteNTimes(' ', indent);
}
if (array_info.sentinel) |sentinel| {
const colon_token = tree.prevToken(sentinel.firstToken());
try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
}
try renderToken(tree, stream, rbracket, indent, start_col, Space.None); // ]
},
.BitNot,
.BoolNot,
.Negation,
.NegationWrap,
.OptionalType,
.AddressOf,
=> {
try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.None);
},
.Try,
.Resume,
=> {
try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.Space);
},
.Await => |await_info| {
try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.Space);
},
.SliceType => {
const slice_type = @fieldParentPtr(ast.Node.SliceType, "base", base);
try renderToken(tree, stream, slice_type.op_token, indent, start_col, Space.None); // [
if (slice_type.ptr_info.sentinel) |sentinel| {
const colon_token = tree.prevToken(sentinel.firstToken());
try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
try renderToken(tree, stream, tree.nextToken(sentinel.lastToken()), indent, start_col, Space.None); // ]
} else {
try renderToken(tree, stream, tree.nextToken(slice_type.op_token), indent, start_col, Space.None); // ]
}
return renderExpression(allocator, stream, tree, indent, start_col, prefix_op_node.rhs, space);
if (slice_type.ptr_info.allowzero_token) |allowzero_token| {
try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero
}
if (slice_type.ptr_info.align_info) |align_info| {
const lparen_token = tree.prevToken(align_info.node.firstToken());
const align_token = tree.prevToken(lparen_token);
try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align
try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None);
if (align_info.bit_range) |bit_range| {
const colon1 = tree.prevToken(bit_range.start.firstToken());
const colon2 = tree.prevToken(bit_range.end.firstToken());
try renderToken(tree, stream, colon1, indent, start_col, Space.None); // :
try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None);
try renderToken(tree, stream, colon2, indent, start_col, Space.None); // :
try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None);
const rparen_token = tree.nextToken(bit_range.end.lastToken());
try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
} else {
const rparen_token = tree.nextToken(align_info.node.lastToken());
try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
}
}
if (slice_type.ptr_info.const_token) |const_token| {
try renderToken(tree, stream, const_token, indent, start_col, Space.Space);
}
if (slice_type.ptr_info.volatile_token) |volatile_token| {
try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space);
}
return renderExpression(allocator, stream, tree, indent, start_col, slice_type.rhs, space);
},
.ArrayInitializer, .ArrayInitializerDot => {
@@ -2057,6 +2083,46 @@ fn renderExpression(
}
}
fn renderArrayType(
allocator: *mem.Allocator,
stream: anytype,
tree: *ast.Tree,
indent: usize,
start_col: *usize,
lbracket: ast.TokenIndex,
rhs: *ast.Node,
len_expr: *ast.Node,
opt_sentinel: ?*ast.Node,
space: Space,
) (@TypeOf(stream).Error || Error)!void {
const rbracket = tree.nextToken(if (opt_sentinel) |sentinel|
sentinel.lastToken()
else
len_expr.lastToken());
try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment;
const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment;
const new_indent = if (ends_with_comment) indent + indent_delta else indent;
const new_space = if (ends_with_comment) Space.Newline else Space.None;
try renderExpression(allocator, stream, tree, new_indent, start_col, len_expr, new_space);
if (starts_with_comment) {
try stream.writeByte('\n');
}
if (ends_with_comment or starts_with_comment) {
try stream.writeByteNTimes(' ', indent);
}
if (opt_sentinel) |sentinel| {
const colon_token = tree.prevToken(sentinel.firstToken());
try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
}
try renderToken(tree, stream, rbracket, indent, start_col, Space.None); // ]
return renderExpression(allocator, stream, tree, indent, start_col, rhs, space);
}
fn renderAsmOutput(
allocator: *mem.Allocator,
stream: anytype,

View File

@@ -36,7 +36,7 @@ bin_file_path: []const u8,
decl_exports: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{},
/// We track which export is associated with the given symbol name for quick
/// detection of symbol collisions.
symbol_exports: std.StringHashMap(*Export),
symbol_exports: std.StringHashMapUnmanaged(*Export) = .{},
/// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl
/// is modified. Note that the key of this table is not the Decl being exported, but the Decl that
/// is performing the export of another Decl.
@@ -769,7 +769,6 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
.bin_file_path = options.bin_file_path,
.bin_file = bin_file,
.optimize_mode = options.optimize_mode,
.symbol_exports = std.StringHashMap(*Export).init(gpa),
.work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa),
.keep_source_files_loaded = options.keep_source_files_loaded,
};
@@ -812,7 +811,7 @@ pub fn deinit(self: *Module) void {
}
self.export_owners.deinit(gpa);
self.symbol_exports.deinit();
self.symbol_exports.deinit(gpa);
self.root_scope.destroy(gpa);
self.* = undefined;
}
@@ -1309,10 +1308,18 @@ fn astGenExpr(self: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir
.ControlFlowExpression => return self.astGenControlFlowExpression(scope, @fieldParentPtr(ast.Node.ControlFlowExpression, "base", ast_node)),
.If => return self.astGenIf(scope, @fieldParentPtr(ast.Node.If, "base", ast_node)),
.InfixOp => return self.astGenInfixOp(scope, @fieldParentPtr(ast.Node.InfixOp, "base", ast_node)),
.BoolNot => return self.astGenBoolNot(scope, @fieldParentPtr(ast.Node.BoolNot, "base", ast_node)),
else => return self.failNode(scope, ast_node, "TODO implement astGenExpr for {}", .{@tagName(ast_node.id)}),
}
}
fn astGenBoolNot(self: *Module, scope: *Scope, node: *ast.Node.BoolNot) InnerError!*zir.Inst {
const operand = try self.astGenExpr(scope, node.rhs);
const tree = scope.tree();
const src = tree.token_locs[node.op_token].start;
return self.addZIRInst(scope, src, zir.Inst.BoolNot, .{ .operand = operand }, .{});
}
fn astGenInfixOp(self: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) InnerError!*zir.Inst {
switch (infix_node.op) {
.Assign => {
@@ -1351,17 +1358,19 @@ fn astGenInfixOp(self: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) In
const tree = scope.tree();
const src = tree.token_locs[infix_node.op_token].start;
const op: std.math.CompareOperator = switch (infix_node.op) {
.BangEqual => .neq,
.EqualEqual => .eq,
.GreaterThan => .gt,
.GreaterOrEqual => .gte,
.LessThan => .lt,
.LessOrEqual => .lte,
else => unreachable,
};
return self.addZIRInst(scope, src, zir.Inst.Cmp, .{
.lhs = lhs,
.op = @as(std.math.CompareOperator, switch (infix_node.op) {
.BangEqual => .neq,
.EqualEqual => .eq,
.GreaterThan => .gt,
.GreaterOrEqual => .gte,
.LessThan => .lt,
.LessOrEqual => .lte,
else => unreachable,
}),
.op = op,
.rhs = rhs,
}, .{});
},
@@ -1408,11 +1417,13 @@ fn astGenIf(self: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir
defer then_scope.instructions.deinit(self.gpa);
const then_result = try self.astGenExpr(&then_scope.base, if_node.body);
const then_src = tree.token_locs[if_node.body.lastToken()].start;
_ = try self.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{
.block = block,
.operand = then_result,
}, .{});
if (!then_result.tag.isNoReturn()) {
const then_src = tree.token_locs[if_node.body.lastToken()].start;
_ = try self.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{
.block = block,
.operand = then_result,
}, .{});
}
condbr.positionals.true_body = .{
.instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items),
};
@@ -1426,11 +1437,13 @@ fn astGenIf(self: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir
if (if_node.@"else") |else_node| {
const else_result = try self.astGenExpr(&else_scope.base, else_node.body);
const else_src = tree.token_locs[else_node.body.lastToken()].start;
_ = try self.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{
.block = block,
.operand = else_result,
}, .{});
if (!else_result.tag.isNoReturn()) {
const else_src = tree.token_locs[else_node.body.lastToken()].start;
_ = try self.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{
.block = block,
.operand = else_result,
}, .{});
}
} else {
// TODO Optimization opportunity: we can avoid an allocation and a memcpy here
// by directly allocating the body for this one instruction.
@@ -2305,7 +2318,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const
return;
}
try self.symbol_exports.putNoClobber(symbol_name, new_export);
try self.symbol_exports.putNoClobber(self.gpa, symbol_name, new_export);
self.bin_file.updateDeclExports(self, exported_decl, de_gop.entry.value) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => {
@@ -2559,6 +2572,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In
.condbr => return self.analyzeInstCondBr(scope, old_inst.cast(zir.Inst.CondBr).?),
.isnull => return self.analyzeInstIsNull(scope, old_inst.cast(zir.Inst.IsNull).?),
.isnonnull => return self.analyzeInstIsNonNull(scope, old_inst.cast(zir.Inst.IsNonNull).?),
.boolnot => return self.analyzeInstBoolNot(scope, old_inst.cast(zir.Inst.BoolNot).?),
}
}
@@ -3236,6 +3250,17 @@ fn analyzeInstCmp(self: *Module, scope: *Scope, inst: *zir.Inst.Cmp) InnerError!
return self.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{});
}
fn analyzeInstBoolNot(self: *Module, scope: *Scope, inst: *zir.Inst.BoolNot) InnerError!*Inst {
const uncasted_operand = try self.resolveInst(scope, inst.positionals.operand);
const bool_type = Type.initTag(.bool);
const operand = try self.coerce(scope, bool_type, uncasted_operand);
if (try self.resolveDefinedValue(scope, operand)) |val| {
return self.constBool(scope, inst.base.src, !val.toBool());
}
const b = try self.requireRuntimeBlock(scope, inst.base.src);
return self.addNewInstArgs(b, inst.base.src, bool_type, Inst.Not, .{ .operand = operand });
}
fn analyzeInstIsNull(self: *Module, scope: *Scope, inst: *zir.Inst.IsNull) InnerError!*Inst {
const operand = try self.resolveInst(scope, inst.positionals.operand);
return self.analyzeIsNull(scope, inst.base.src, operand, true);

View File

@@ -407,6 +407,55 @@ const Function = struct {
.retvoid => return self.genRetVoid(inst.cast(ir.Inst.RetVoid).?, arch),
.sub => return self.genSub(inst.cast(ir.Inst.Sub).?, arch),
.unreach => return MCValue{ .unreach = {} },
.not => return self.genNot(inst.cast(ir.Inst.Not).?, arch),
}
}
fn genNot(self: *Function, inst: *ir.Inst.Not, comptime arch: std.Target.Cpu.Arch) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
const operand = try self.resolveInst(inst.args.operand);
switch (operand) {
.dead => unreachable,
.unreach => unreachable,
.compare_flags_unsigned => |op| return MCValue{
.compare_flags_unsigned = switch (op) {
.gte => .lt,
.gt => .lte,
.neq => .eq,
.lt => .gte,
.lte => .gt,
.eq => .neq,
},
},
.compare_flags_signed => |op| return MCValue{
.compare_flags_signed = switch (op) {
.gte => .lt,
.gt => .lte,
.neq => .eq,
.lt => .gte,
.lte => .gt,
.eq => .neq,
},
},
else => {},
}
switch (arch) {
.x86_64 => {
var imm = ir.Inst.Constant{
.base = .{
.tag = .constant,
.deaths = 0,
.ty = inst.args.operand.ty,
.src = inst.args.operand.src,
},
.val = Value.initTag(.bool_true),
};
return try self.genX8664BinMath(&inst.base, inst.args.operand, &imm.base, 6, 0x30);
},
else => return self.fail(inst.base.src, "TODO implement NOT for {}", .{self.target.cpu.arch}),
}
}
@@ -434,7 +483,7 @@ const Function = struct {
}
}
/// ADD, SUB
/// ADD, SUB, XOR, OR, AND
fn genX8664BinMath(self: *Function, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst, opx: u8, mr: u8) !MCValue {
try self.code.ensureCapacity(self.code.items.len + 8);
@@ -695,7 +744,7 @@ const Function = struct {
fn genCondBr(self: *Function, inst: *ir.Inst.CondBr, comptime arch: std.Target.Cpu.Arch) !MCValue {
switch (arch) {
.i386, .x86_64 => {
.x86_64 => {
try self.code.ensureCapacity(self.code.items.len + 6);
const cond = try self.resolveInst(inst.args.condition);
@@ -724,7 +773,20 @@ const Function = struct {
};
return self.genX86CondBr(inst, opcode, arch);
},
else => return self.fail(inst.base.src, "TODO implement condbr {} when condition not already in the compare flags", .{self.target.cpu.arch}),
.register => |reg_usize| {
const reg = @intToEnum(Reg(arch), @intCast(u8, reg_usize));
// test reg, 1
// TODO detect al, ax, eax
try self.code.ensureCapacity(self.code.items.len + 4);
self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 });
self.code.appendSliceAssumeCapacity(&[_]u8{
0xf6,
@as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()),
0x01,
});
return self.genX86CondBr(inst, 0x84, arch);
},
else => return self.fail(inst.base.src, "TODO implement condbr {} when condition is {}", .{ self.target.cpu.arch, @tagName(cond) }),
}
},
else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}),
@@ -812,6 +874,8 @@ const Function = struct {
}
fn genAsm(self: *Function, inst: *ir.Inst.Assembly, comptime arch: Target.Cpu.Arch) !MCValue {
if (!inst.args.is_volatile and inst.base.isUnused())
return MCValue.dead;
if (arch != .x86_64 and arch != .i386) {
return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{});
}
@@ -880,7 +944,18 @@ const Function = struct {
.none => unreachable,
.unreach => unreachable,
.compare_flags_unsigned => |op| {
return self.fail(src, "TODO set register with compare flags value (unsigned)", .{});
try self.code.ensureCapacity(self.code.items.len + 3);
self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 });
const opcode: u8 = switch (op) {
.gte => 0x93,
.gt => 0x97,
.neq => 0x95,
.lt => 0x92,
.lte => 0x96,
.eq => 0x94,
};
const id = @as(u8, reg.id() & 0b111);
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode, 0xC0 | id });
},
.compare_flags_signed => |op| {
return self.fail(src, "TODO set register with compare flags value (signed)", .{});
@@ -1135,6 +1210,9 @@ const Function = struct {
}
return MCValue{ .immediate = typed_value.val.toUnsignedInt() };
},
.Bool => {
return MCValue{ .immediate = @boolToInt(typed_value.val.toBool()) };
},
.ComptimeInt => unreachable, // semantic analysis prevents this
.ComptimeFloat => unreachable, // semantic analysis prevents this
else => return self.fail(src, "TODO implement const of type '{}'", .{typed_value.ty}),

View File

@@ -14,30 +14,35 @@ pub const Inst = struct {
tag: Tag,
/// Each bit represents the index of an `Inst` parameter in the `args` field.
/// If a bit is set, it marks the end of the lifetime of the corresponding
/// instruction parameter. For example, 0b000_00101 means that the first and
/// instruction parameter. For example, 0b101 means that the first and
/// third `Inst` parameters' lifetimes end after this instruction, and will
/// not have any more following references.
/// The most significant bit being set means that the instruction itself is
/// never referenced, in other words its lifetime ends as soon as it finishes.
/// If bit 7 (0b1xxx_xxxx) is set, it means this instruction itself is unreferenced.
/// If bit 6 (0bx1xx_xxxx) is set, it means this is a special case and the
/// If bit 15 (0b1xxx_xxxx_xxxx_xxxx) is set, it means this instruction itself is unreferenced.
/// If bit 14 (0bx1xx_xxxx_xxxx_xxxx) is set, it means this is a special case and the
/// lifetimes of operands are encoded elsewhere.
deaths: u8 = undefined,
deaths: DeathsInt = undefined,
ty: Type,
/// Byte offset into the source.
src: usize,
pub const DeathsInt = u16;
pub const DeathsBitIndex = std.math.Log2Int(DeathsInt);
pub const unreferenced_bit_index = @typeInfo(DeathsInt).Int.bits - 1;
pub const deaths_bits = unreferenced_bit_index - 1;
pub fn isUnused(self: Inst) bool {
return (self.deaths & 0b1000_0000) != 0;
return (self.deaths & (1 << unreferenced_bit_index)) != 0;
}
pub fn operandDies(self: Inst, index: u3) bool {
assert(index < 6);
pub fn operandDies(self: Inst, index: DeathsBitIndex) bool {
assert(index < deaths_bits);
return @truncate(u1, self.deaths << index) != 0;
}
pub fn specialOperandDeaths(self: Inst) bool {
return (self.deaths & 0b1000_0000) != 0;
return (self.deaths & (1 << deaths_bits)) != 0;
}
pub const Tag = enum {
@@ -60,6 +65,7 @@ pub const Inst = struct {
retvoid,
sub,
unreach,
not,
};
pub fn cast(base: *Inst, comptime T: type) ?*T {
@@ -194,6 +200,15 @@ pub const Inst = struct {
false_death_count: u32 = 0,
};
pub const Not = struct {
pub const base_tag = Tag.not;
base: Inst,
args: struct {
operand: *Inst,
},
};
pub const Constant = struct {
pub const base_tag = Tag.constant;
base: Inst,

View File

@@ -34,7 +34,7 @@ fn analyzeInstGeneric(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Ins
inline for (std.meta.declarations(ir.Inst)) |decl| {
switch (decl.data) {
.Type => |T| {
if (@hasDecl(T, "base_tag")) {
if (@typeInfo(T) == .Struct and @hasDecl(T, "base_tag")) {
if (T.base_tag == base.tag) {
return analyzeInst(arena, table, T, @fieldParentPtr(T, "base", base));
}
@@ -47,7 +47,13 @@ fn analyzeInstGeneric(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Ins
}
fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), comptime T: type, inst: *T) error{OutOfMemory}!void {
inst.base.deaths = 0;
if (table.contains(&inst.base)) {
inst.base.deaths = 0;
} else {
// No tombstone for this instruction means it is never referenced,
// and its birth marks its own death. Very metal 🤘
inst.base.deaths = 1 << ir.Inst.unreferenced_bit_index;
}
switch (T) {
ir.Inst.Constant => return,
@@ -106,15 +112,28 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void
// instruction, and the deaths flag for the CondBr instruction will indicate whether the
// condition's lifetime ends immediately before entering any branch.
},
ir.Inst.Call => {
// Call instructions have a runtime-known number of operands so we have to handle them ourselves here.
const needed_bits = 1 + inst.args.args.len;
if (needed_bits <= ir.Inst.deaths_bits) {
var bit_i: ir.Inst.DeathsBitIndex = 0;
{
const prev = try table.fetchPut(inst.args.func, {});
if (prev == null) inst.base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i;
bit_i += 1;
}
for (inst.args.args) |arg| {
const prev = try table.fetchPut(arg, {});
if (prev == null) inst.base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i;
bit_i += 1;
}
} else {
@panic("Handle liveness analysis for function calls with many parameters");
}
},
else => {},
}
if (!table.contains(&inst.base)) {
// No tombstone for this instruction means it is never referenced,
// and its birth marks its own death. Very metal 🤘
inst.base.deaths |= 1 << 7;
}
const Args = ir.Inst.Args(T);
if (Args == void) {
return;

View File

@@ -1561,7 +1561,7 @@ fn transImplicitCastExpr(
return maybeSuppressResult(rp, scope, result_used, sub_expr_node);
}
const prefix_op = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
const prefix_op = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
prefix_op.rhs = try transExpr(rp, scope, sub_expr, .used, .r_value);
return maybeSuppressResult(rp, scope, result_used, &prefix_op.base);
@@ -1673,11 +1673,7 @@ fn isBoolRes(res: *ast.Node) bool {
else => {},
},
.PrefixOp => switch (@fieldParentPtr(ast.Node.PrefixOp, "base", res).op) {
.BoolNot => return true,
else => {},
},
.BoolNot => return true,
.BoolLiteral => return true,
.GroupedExpression => return isBoolRes(@fieldParentPtr(ast.Node.GroupedExpression, "base", res).expr),
else => {},
@@ -2162,21 +2158,16 @@ fn transCreateNodeArrayType(
source_loc: ZigClangSourceLocation,
ty: *const ZigClangType,
len: anytype,
) TransError!*ast.Node {
var node = try transCreateNodePrefixOp(
rp.c,
.{
.ArrayType = .{
.len_expr = undefined,
.sentinel = null,
},
},
.LBracket,
"[",
);
node.op.ArrayType.len_expr = try transCreateNodeInt(rp.c, len);
) !*ast.Node {
const node = try rp.c.arena.create(ast.Node.ArrayType);
const op_token = try appendToken(rp.c, .LBracket, "[");
const len_expr = try transCreateNodeInt(rp.c, len);
_ = try appendToken(rp.c, .RBracket, "]");
node.rhs = try transType(rp, ty, source_loc);
node.* = .{
.op_token = op_token,
.rhs = try transType(rp, ty, source_loc),
.len_expr = len_expr,
};
return &node.base;
}
@@ -2449,7 +2440,7 @@ fn transDoWhileLoop(
},
};
defer cond_scope.deinit();
const prefix_op = try transCreateNodePrefixOp(rp.c, .BoolNot, .Bang, "!");
const prefix_op = try transCreateNodeSimplePrefixOp(rp.c, .BoolNot, .Bang, "!");
prefix_op.rhs = try transBoolExpr(rp, &cond_scope.base, @ptrCast(*const ZigClangExpr, ZigClangDoStmt_getCond(stmt)), .used, .r_value, true);
_ = try appendToken(rp.c, .RParen, ")");
if_node.condition = &prefix_op.base;
@@ -3036,7 +3027,7 @@ fn transUnaryOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnar
else
return transCreatePreCrement(rp, scope, stmt, .AssignSub, .MinusEqual, "-=", used),
.AddrOf => {
const op_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
const op_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
op_node.rhs = try transExpr(rp, scope, op_expr, used, .r_value);
return &op_node.base;
},
@@ -3052,7 +3043,7 @@ fn transUnaryOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnar
.Plus => return transExpr(rp, scope, op_expr, used, .r_value),
.Minus => {
if (!qualTypeHasWrappingOverflow(ZigClangExpr_getType(op_expr))) {
const op_node = try transCreateNodePrefixOp(rp.c, .Negation, .Minus, "-");
const op_node = try transCreateNodeSimplePrefixOp(rp.c, .Negation, .Minus, "-");
op_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value);
return &op_node.base;
} else if (cIsUnsignedInteger(ZigClangExpr_getType(op_expr))) {
@@ -3065,12 +3056,12 @@ fn transUnaryOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnar
return revertAndWarn(rp, error.UnsupportedTranslation, ZigClangUnaryOperator_getBeginLoc(stmt), "C negation with non float non integer", .{});
},
.Not => {
const op_node = try transCreateNodePrefixOp(rp.c, .BitNot, .Tilde, "~");
const op_node = try transCreateNodeSimplePrefixOp(rp.c, .BitNot, .Tilde, "~");
op_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value);
return &op_node.base;
},
.LNot => {
const op_node = try transCreateNodePrefixOp(rp.c, .BoolNot, .Bang, "!");
const op_node = try transCreateNodeSimplePrefixOp(rp.c, .BoolNot, .Bang, "!");
op_node.rhs = try transBoolExpr(rp, scope, op_expr, .used, .r_value, true);
return &op_node.base;
},
@@ -3116,7 +3107,7 @@ fn transCreatePreCrement(
const node = try transCreateNodeVarDecl(rp.c, false, true, ref);
node.eq_token = try appendToken(rp.c, .Equal, "=");
const rhs_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
const rhs_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
rhs_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value);
node.init_node = &rhs_node.base;
node.semicolon_token = try appendToken(rp.c, .Semicolon, ";");
@@ -3182,7 +3173,7 @@ fn transCreatePostCrement(
const node = try transCreateNodeVarDecl(rp.c, false, true, ref);
node.eq_token = try appendToken(rp.c, .Equal, "=");
const rhs_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
const rhs_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
rhs_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value);
node.init_node = &rhs_node.base;
node.semicolon_token = try appendToken(rp.c, .Semicolon, ";");
@@ -3336,7 +3327,7 @@ fn transCreateCompoundAssign(
const node = try transCreateNodeVarDecl(rp.c, false, true, ref);
node.eq_token = try appendToken(rp.c, .Equal, "=");
const addr_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
const addr_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
addr_node.rhs = try transExpr(rp, scope, lhs, .used, .l_value);
node.init_node = &addr_node.base;
node.semicolon_token = try appendToken(rp.c, .Semicolon, ";");
@@ -3984,16 +3975,15 @@ fn transCreateNodeFieldAccess(c: *Context, container: *ast.Node, field_name: []c
return &field_access_node.base;
}
fn transCreateNodePrefixOp(
fn transCreateNodeSimplePrefixOp(
c: *Context,
op: ast.Node.PrefixOp.Op,
comptime tag: ast.Node.Id,
op_tok_id: std.zig.Token.Id,
bytes: []const u8,
) !*ast.Node.PrefixOp {
const node = try c.arena.create(ast.Node.PrefixOp);
) !*ast.Node.SimplePrefixOp(tag) {
const node = try c.arena.create(ast.Node.SimplePrefixOp(tag));
node.* = .{
.op_token = try appendToken(c, op_tok_id, bytes),
.op = op,
.rhs = undefined, // translate and set afterward
};
return node;
@@ -4065,8 +4055,8 @@ fn transCreateNodePtrType(
is_const: bool,
is_volatile: bool,
op_tok_id: std.zig.Token.Id,
) !*ast.Node.PrefixOp {
const node = try c.arena.create(ast.Node.PrefixOp);
) !*ast.Node.PtrType {
const node = try c.arena.create(ast.Node.PtrType);
const op_token = switch (op_tok_id) {
.LBracket => blk: {
const lbracket = try appendToken(c, .LBracket, "[");
@@ -4086,11 +4076,9 @@ fn transCreateNodePtrType(
};
node.* = .{
.op_token = op_token,
.op = .{
.PtrType = .{
.const_token = if (is_const) try appendToken(c, .Keyword_const, "const") else null,
.volatile_token = if (is_volatile) try appendToken(c, .Keyword_volatile, "volatile") else null,
},
.ptr_info = .{
.const_token = if (is_const) try appendToken(c, .Keyword_const, "const") else null,
.volatile_token = if (is_volatile) try appendToken(c, .Keyword_volatile, "volatile") else null,
},
.rhs = undefined, // translate and set afterward
};
@@ -4569,12 +4557,12 @@ fn transType(rp: RestorePoint, ty: *const ZigClangType, source_loc: ZigClangSour
.Pointer => {
const child_qt = ZigClangType_getPointeeType(ty);
if (qualTypeChildIsFnProto(child_qt)) {
const optional_node = try transCreateNodePrefixOp(rp.c, .OptionalType, .QuestionMark, "?");
const optional_node = try transCreateNodeSimplePrefixOp(rp.c, .OptionalType, .QuestionMark, "?");
optional_node.rhs = try transQualType(rp, child_qt, source_loc);
return &optional_node.base;
}
if (typeIsOpaque(rp.c, ZigClangQualType_getTypePtr(child_qt), source_loc)) {
const optional_node = try transCreateNodePrefixOp(rp.c, .OptionalType, .QuestionMark, "?");
const optional_node = try transCreateNodeSimplePrefixOp(rp.c, .OptionalType, .QuestionMark, "?");
const pointer_node = try transCreateNodePtrType(
rp.c,
ZigClangQualType_isConstQualified(child_qt),
@@ -4599,21 +4587,8 @@ fn transType(rp: RestorePoint, ty: *const ZigClangType, source_loc: ZigClangSour
const size_ap_int = ZigClangConstantArrayType_getSize(const_arr_ty);
const size = ZigClangAPInt_getLimitedValue(size_ap_int, math.maxInt(usize));
var node = try transCreateNodePrefixOp(
rp.c,
.{
.ArrayType = .{
.len_expr = undefined,
.sentinel = null,
},
},
.LBracket,
"[",
);
node.op.ArrayType.len_expr = try transCreateNodeInt(rp.c, size);
_ = try appendToken(rp.c, .RBracket, "]");
node.rhs = try transQualType(rp, ZigClangConstantArrayType_getElementType(const_arr_ty), source_loc);
return &node.base;
const elem_ty = ZigClangQualType_getTypePtr(ZigClangConstantArrayType_getElementType(const_arr_ty));
return try transCreateNodeArrayType(rp, source_loc, elem_ty, size);
},
.IncompleteArray => {
const incomplete_array_ty = @ptrCast(*const ZigClangIncompleteArrayType, ty);
@@ -5824,7 +5799,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
if (prev_id == .Keyword_void) {
const ptr = try transCreateNodePtrType(c, false, false, .Asterisk);
ptr.rhs = node;
const optional_node = try transCreateNodePrefixOp(c, .OptionalType, .QuestionMark, "?");
const optional_node = try transCreateNodeSimplePrefixOp(c, .OptionalType, .QuestionMark, "?");
optional_node.rhs = &ptr.base;
return &optional_node.base;
} else {
@@ -5993,18 +5968,18 @@ fn parseCPrefixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
switch (op_tok.id) {
.Bang => {
const node = try transCreateNodePrefixOp(c, .BoolNot, .Bang, "!");
const node = try transCreateNodeSimplePrefixOp(c, .BoolNot, .Bang, "!");
node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
return &node.base;
},
.Minus => {
const node = try transCreateNodePrefixOp(c, .Negation, .Minus, "-");
const node = try transCreateNodeSimplePrefixOp(c, .Negation, .Minus, "-");
node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
return &node.base;
},
.Plus => return try parseCPrefixOpExpr(c, it, source, source_loc, scope),
.Tilde => {
const node = try transCreateNodePrefixOp(c, .BitNot, .Tilde, "~");
const node = try transCreateNodeSimplePrefixOp(c, .BitNot, .Tilde, "~");
node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
return &node.base;
},
@@ -6013,7 +5988,7 @@ fn parseCPrefixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
return try transCreateNodePtrDeref(c, node);
},
.Ampersand => {
const node = try transCreateNodePrefixOp(c, .AddressOf, .Ampersand, "&");
const node = try transCreateNodeSimplePrefixOp(c, .AddressOf, .Ampersand, "&");
node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
return &node.base;
},
@@ -6034,29 +6009,49 @@ fn tokenSlice(c: *Context, token: ast.TokenIndex) []u8 {
}
fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node {
if (node.id == .ContainerDecl) {
return node;
} else if (node.id == .PrefixOp) {
return node;
} else if (node.cast(ast.Node.Identifier)) |ident| {
if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| {
if (value.cast(ast.Node.VarDecl)) |var_decl|
return getContainer(c, var_decl.init_node.?);
}
} else if (node.cast(ast.Node.InfixOp)) |infix| {
if (infix.op != .Period)
return null;
if (getContainerTypeOf(c, infix.lhs)) |ty_node| {
if (ty_node.cast(ast.Node.ContainerDecl)) |container| {
for (container.fieldsAndDecls()) |field_ref| {
const field = field_ref.cast(ast.Node.ContainerField).?;
const ident = infix.rhs.cast(ast.Node.Identifier).?;
if (mem.eql(u8, tokenSlice(c, field.name_token), tokenSlice(c, ident.token))) {
return getContainer(c, field.type_expr.?);
switch (node.id) {
.ContainerDecl,
.AddressOf,
.Await,
.BitNot,
.BoolNot,
.OptionalType,
.Negation,
.NegationWrap,
.Resume,
.Try,
.ArrayType,
.ArrayTypeSentinel,
.PtrType,
.SliceType,
=> return node,
.Identifier => {
const ident = node.cast(ast.Node.Identifier).?;
if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| {
if (value.cast(ast.Node.VarDecl)) |var_decl|
return getContainer(c, var_decl.init_node.?);
}
},
.InfixOp => {
const infix = node.cast(ast.Node.InfixOp).?;
if (infix.op != .Period)
return null;
if (getContainerTypeOf(c, infix.lhs)) |ty_node| {
if (ty_node.cast(ast.Node.ContainerDecl)) |container| {
for (container.fieldsAndDecls()) |field_ref| {
const field = field_ref.cast(ast.Node.ContainerField).?;
const ident = infix.rhs.cast(ast.Node.Identifier).?;
if (mem.eql(u8, tokenSlice(c, field.name_token), tokenSlice(c, ident.token))) {
return getContainer(c, field.type_expr.?);
}
}
}
}
}
},
else => {},
}
return null;
}
@@ -6091,11 +6086,9 @@ fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node {
fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto {
const init = if (ref.cast(ast.Node.VarDecl)) |v| v.init_node.? else return null;
if (getContainerTypeOf(c, init)) |ty_node| {
if (ty_node.cast(ast.Node.PrefixOp)) |prefix| {
if (prefix.op == .OptionalType) {
if (prefix.rhs.cast(ast.Node.FnProto)) |fn_proto| {
return fn_proto;
}
if (ty_node.cast(ast.Node.OptionalType)) |prefix| {
if (prefix.rhs.cast(ast.Node.FnProto)) |fn_proto| {
return fn_proto;
}
}
}

View File

@@ -163,6 +163,22 @@ pub const Type = extern union {
return sentinel_b == null;
}
},
.Fn => {
if (!a.fnReturnType().eql(b.fnReturnType()))
return false;
if (a.fnCallingConvention() != b.fnCallingConvention())
return false;
const a_param_len = a.fnParamLen();
const b_param_len = b.fnParamLen();
if (a_param_len != b_param_len)
return false;
var i: usize = 0;
while (i < a_param_len) : (i += 1) {
if (!a.fnParamType(i).eql(b.fnParamType(i)))
return false;
}
return true;
},
.Float,
.Struct,
.Optional,
@@ -170,14 +186,13 @@ pub const Type = extern union {
.ErrorSet,
.Enum,
.Union,
.Fn,
.BoundFn,
.Opaque,
.Frame,
.AnyFrame,
.Vector,
.EnumLiteral,
=> @panic("TODO implement more Type equality comparison"),
=> std.debug.panic("TODO implement Type equality comparison of {} and {}", .{ a, b }),
}
}

View File

@@ -427,8 +427,6 @@ pub const Value = extern union {
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.bool_true,
.bool_false,
.null_value,
.function,
.ref_val,
@@ -441,8 +439,11 @@ pub const Value = extern union {
.the_one_possible_value, // An integer with one possible value is always zero.
.zero,
.bool_false,
=> return BigIntMutable.init(&space.limbs, 0).toConst(),
.bool_true => return BigIntMutable.init(&space.limbs, 1).toConst(),
.int_u64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_u64).?.int).toConst(),
.int_i64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_i64).?.int).toConst(),
.int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt(),
@@ -493,8 +494,6 @@ pub const Value = extern union {
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.bool_true,
.bool_false,
.null_value,
.function,
.ref_val,
@@ -507,8 +506,11 @@ pub const Value = extern union {
.zero,
.the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> return 0,
.bool_true => return 1,
.int_u64 => return self.cast(Payload.Int_u64).?.int,
.int_i64 => return @intCast(u64, self.cast(Payload.Int_u64).?.int),
.int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().to(u64) catch unreachable,
@@ -560,8 +562,6 @@ pub const Value = extern union {
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.bool_true,
.bool_false,
.null_value,
.function,
.ref_val,
@@ -574,8 +574,11 @@ pub const Value = extern union {
.the_one_possible_value, // an integer with one possible value is always zero
.zero,
.bool_false,
=> return 0,
.bool_true => return 1,
.int_u64 => {
const x = self.cast(Payload.Int_u64).?.int;
if (x == 0) return 0;
@@ -632,8 +635,6 @@ pub const Value = extern union {
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.bool_true,
.bool_false,
.null_value,
.function,
.ref_val,
@@ -646,8 +647,18 @@ pub const Value = extern union {
.zero,
.undef,
.the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> return true,
.bool_true => {
const info = ty.intInfo(target);
if (info.signed) {
return info.bits >= 2;
} else {
return info.bits >= 1;
}
},
.int_u64 => switch (ty.zigTypeTag()) {
.Int => {
const x = self.cast(Payload.Int_u64).?.int;
@@ -796,8 +807,6 @@ pub const Value = extern union {
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.bool_true,
.bool_false,
.null_value,
.function,
.ref_val,
@@ -810,8 +819,11 @@ pub const Value = extern union {
.zero,
.the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> return .eq,
.bool_true => return .gt,
.int_u64 => return std.math.order(lhs.cast(Payload.Int_u64).?.int, 0),
.int_i64 => return std.math.order(lhs.cast(Payload.Int_i64).?.int, 0),
.int_big_positive => return lhs.cast(Payload.IntBigPositive).?.asBigInt().orderAgainstScalar(0),
@@ -855,7 +867,7 @@ pub const Value = extern union {
pub fn toBool(self: Value) bool {
return switch (self.tag()) {
.bool_true => true,
.bool_false => false,
.bool_false, .zero => false,
else => unreachable,
};
}

View File

@@ -56,6 +56,7 @@ pub const Inst = struct {
declval,
/// Same as declval but the parameter is a `*Module.Decl` rather than a name.
declval_in_module,
boolnot,
/// String Literal. Makes an anonymous Decl and then takes a pointer to it.
str,
int,
@@ -115,6 +116,7 @@ pub const Inst = struct {
.cmp,
.isnull,
.isnonnull,
.boolnot,
=> false,
.condbr,
@@ -143,6 +145,7 @@ pub const Inst = struct {
.declval_in_module => DeclValInModule,
.compileerror => CompileError,
.@"const" => Const,
.boolnot => BoolNot,
.str => Str,
.int => Int,
.inttype => IntType,
@@ -299,6 +302,16 @@ pub const Inst = struct {
kw_args: struct {},
};
pub const BoolNot = struct {
pub const base_tag = Tag.boolnot;
base: Inst,
positionals: struct {
operand: *Inst,
},
kw_args: struct {},
};
pub const Str = struct {
pub const base_tag = Tag.str;
base: Inst,
@@ -762,6 +775,7 @@ const Writer = struct {
.declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, inst),
.compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, inst),
.@"const" => return self.writeInstToStreamGeneric(stream, .@"const", inst),
.boolnot => return self.writeInstToStreamGeneric(stream, .boolnot, inst),
.str => return self.writeInstToStreamGeneric(stream, .str, inst),
.int => return self.writeInstToStreamGeneric(stream, .int, inst),
.inttype => return self.writeInstToStreamGeneric(stream, .inttype, inst),
@@ -1658,6 +1672,22 @@ const EmitZIR = struct {
};
for (body.instructions) |inst| {
const new_inst = switch (inst.tag) {
.not => blk: {
const old_inst = inst.cast(ir.Inst.Not).?;
assert(inst.ty.zigTypeTag() == .Bool);
const new_inst = try self.arena.allocator.create(Inst.BoolNot);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.BoolNot.base_tag,
},
.positionals = .{
.operand = try self.resolveInst(new_body, old_inst.args.operand),
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
.add => blk: {
const old_inst = inst.cast(ir.Inst.Add).?;
const new_inst = try self.arena.allocator.create(Inst.Add);

View File

@@ -170,4 +170,61 @@ pub fn addCases(ctx: *TestContext) !void {
"",
);
}
{
var case = ctx.exe("assert function", linux_x64);
case.addCompareOutput(
\\export fn _start() noreturn {
\\ add(3, 4);
\\
\\ exit();
\\}
\\
\\fn add(a: u32, b: u32) void {
\\ assert(a + b == 7);
\\}
\\
\\pub fn assert(ok: bool) void {
\\ if (!ok) unreachable; // assertion failure
\\}
\\
\\fn exit() noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (0)
\\ : "rcx", "r11", "memory"
\\ );
\\ unreachable;
\\}
,
"",
);
case.addCompareOutput(
\\export fn _start() noreturn {
\\ add(100, 200);
\\
\\ exit();
\\}
\\
\\fn add(a: u32, b: u32) void {
\\ assert(a + b == 300);
\\}
\\
\\pub fn assert(ok: bool) void {
\\ if (!ok) unreachable; // assertion failure
\\}
\\
\\fn exit() noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (0)
\\ : "rcx", "r11", "memory"
\\ );
\\ unreachable;
\\}
,
"",
);
}
}