Skip to content

Commit

Permalink
Parser: implement frontend support for __builtin_complex
Browse files Browse the repository at this point in the history
  • Loading branch information
ehaas committed Sep 3, 2023
1 parent 20b2123 commit 7fc1f62
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 11 deletions.
10 changes: 10 additions & 0 deletions src/Diagnostics.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2401,6 +2401,16 @@ const messages = struct {
const kind = .off;
const pedantic = true;
};
pub const not_floating_type = struct {
const msg = "argument type '{s}' is not a real floating point type";
const extra = .str;
const kind = .@"error";
};
pub const argument_types_differ = struct {
const msg = "arguments are of different types ({s})";
const extra = .str;
const kind = .@"error";
};
};

list: std.ArrayListUnmanaged(Message) = .{},
Expand Down
67 changes: 58 additions & 9 deletions src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4571,6 +4571,7 @@ const CallExpr = union(enum) {
.standard => true,
.builtin => |builtin| switch (builtin.tag) {
.__builtin_va_start, .__va_start, .va_start => arg_idx != 1,
.__builtin_complex => false,
else => true,
},
};
Expand All @@ -4588,16 +4589,46 @@ const CallExpr = union(enum) {
const builtin_tok = p.nodes.items(.data)[@intFromEnum(self.builtin.node)].decl.name;
switch (self.builtin.tag) {
.__builtin_va_start, .__va_start, .va_start => return p.checkVaStartArg(builtin_tok, first_after, param_tok, arg, arg_idx),
.__builtin_complex => return p.checkComplexArg(builtin_tok, first_after, param_tok, arg, arg_idx),
else => {},
}
}

/// Some functions cannot be expressed as standard C prototypes. For example `__builtin_complex` requires
/// two arguments of the same real floating point type (e.g. two doubles or two floats). These functions are
/// encoded as varargs functions with custom typechecking. Since varargs functions do not have a fixed number
/// of arguments, `paramCountOverride` is used to tell us how many arguments we should actually expect to see for
/// these custom-typechecked functions.
fn paramCountOverride(self: CallExpr) ?u32 {
return switch (self) {
.standard => null,
.builtin => |builtin| switch (builtin.tag) {
.__builtin_complex => 2,
else => null,
},
};
}

fn returnType(self: CallExpr, p: *Parser, callable_ty: Type) Type {
return switch (self) {
.standard => callable_ty.returnType(),
.builtin => |builtin| switch (builtin.tag) {
.__builtin_complex => {
const last_param = p.list_buf.items[p.list_buf.items.len - 1];
return p.nodes.items(.ty)[@intFromEnum(last_param)].makeComplex();
},
else => callable_ty.returnType(),
},
};
}

fn finish(self: CallExpr, p: *Parser, ty: Type, list_buf_top: usize, arg_count: u32) Error!Result {
const ret_ty = self.returnType(p, ty);
switch (self) {
.standard => |func_node| {
var call_node: Tree.Node = .{
.tag = .call_expr_one,
.ty = ty.returnType(),
.ty = ret_ty,
.data = .{ .bin = .{ .lhs = func_node, .rhs = .none } },
};
const args = p.list_buf.items[list_buf_top..];
Expand All @@ -4609,12 +4640,13 @@ const CallExpr = union(enum) {
call_node.data = .{ .range = try p.addList(args) };
},
}
return Result{ .node = try p.addNode(call_node), .ty = call_node.ty };
return Result{ .node = try p.addNode(call_node), .ty = ret_ty };
},
.builtin => |builtin| {
const index = @intFromEnum(builtin.node);
var call_node = p.nodes.get(index);
defer p.nodes.set(index, call_node);
call_node.ty = ret_ty;
const args = p.list_buf.items[list_buf_top..];
switch (arg_count) {
0 => {},
Expand All @@ -4625,7 +4657,7 @@ const CallExpr = union(enum) {
call_node.data = .{ .range = try p.addList(args) };
},
}
return Result{ .node = builtin.node, .ty = call_node.ty.returnType() };
return Result{ .node = builtin.node, .ty = ret_ty };
},
}
}
Expand Down Expand Up @@ -7003,6 +7035,20 @@ fn checkVaStartArg(p: *Parser, builtin_tok: TokenIndex, first_after: TokenIndex,
}
}

fn checkComplexArg(p: *Parser, builtin_tok: TokenIndex, first_after: TokenIndex, param_tok: TokenIndex, arg: *Result, idx: u32) !void {
_ = builtin_tok;
_ = first_after;
if (idx <= 1 and !arg.ty.isFloat()) {
try p.errStr(.not_floating_type, param_tok, try p.typeStr(arg.ty));
} else if (idx == 1) {
const prev_idx = p.list_buf.items[p.list_buf.items.len - 1];
const prev_ty = p.nodes.items(.ty)[@intFromEnum(prev_idx)];
if (!prev_ty.eql(arg.ty, p.comp, false)) {
try p.errStr(.argument_types_differ, param_tok, try p.typePairStrExtra(prev_ty, " vs ", arg.ty));
}
}
}

fn checkVariableBuiltinArgument(p: *Parser, builtin_tok: TokenIndex, first_after: TokenIndex, param_tok: TokenIndex, arg: *Result, arg_idx: u32, tag: BuiltinFunction.Tag) !void {
switch (tag) {
.__builtin_va_start, .__va_start, .va_start => return p.checkVaStartArg(builtin_tok, first_after, param_tok, arg, arg_idx),
Expand Down Expand Up @@ -7070,17 +7116,20 @@ fn callExpr(p: *Parser, lhs: Result) Error!Result {
};
}

const actual: u32 = @intCast(arg_count);
const extra = Diagnostics.Message.Extra{ .arguments = .{
.expected = @intCast(params.len),
.actual = @intCast(arg_count),
.actual = actual,
} };
if (ty.is(.func) and params.len != arg_count) {
if (call_expr.paramCountOverride()) |expected| {
if (expected != actual) {
try p.errExtra(.expected_arguments, first_after, .{ .arguments = .{ .expected = expected, .actual = actual } });
}
} else if (ty.is(.func) and params.len != arg_count) {
try p.errExtra(.expected_arguments, first_after, extra);
}
if (ty.is(.old_style_func) and params.len != arg_count) {
} else if (ty.is(.old_style_func) and params.len != arg_count) {
try p.errExtra(.expected_arguments_old, first_after, extra);
}
if (ty.is(.var_args_func) and arg_count < params.len) {
} else if (ty.is(.var_args_func) and arg_count < params.len) {
try p.errExtra(.expected_at_least_arguments, first_after, extra);
}

Expand Down
4 changes: 2 additions & 2 deletions test/cases/ast/_Float16.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn_def: 'fn (x: int, ...) void'
var: '[1]struct __va_list_tag'
name: va

builtin_call_expr: 'fn (d[1]struct __va_list_tag, ...) void'
builtin_call_expr: 'void'
name: __builtin_va_start
args:
implicit_cast: (array_to_pointer) 'd[1]struct __va_list_tag'
Expand All @@ -36,7 +36,7 @@ fn_def: 'fn (x: int, ...) void'
decl_ref_expr: 'int' lvalue
name: x

builtin_call_expr_one: 'fn (d[1]struct __va_list_tag) void'
builtin_call_expr_one: 'void'
name: __builtin_va_end
arg:
implicit_cast: (array_to_pointer) 'd[1]struct __va_list_tag'
Expand Down
6 changes: 6 additions & 0 deletions test/cases/complex numbers clang.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ void foo(int x, float y) {

_Complex double z = (_Complex double){1.0, 2.0};
z++;

z = __builtin_complex(2.0, 3);
z = __builtin_complex(2.0, 3.0f);
z = __builtin_complex(2.0, 3.0);
}

#define EXPECTED_ERRORS "complex numbers clang.c:5:20: error: static_assert expression is not an integral constant expression" \
"complex numbers clang.c:6:20: error: static_assert expression is not an integral constant expression" \
"complex numbers clang.c:7:20: error: static_assert expression is not an integral constant expression" \
"complex numbers clang.c:9:42: warning: complex initialization specifying real and imaginary components is an extension [-Wcomplex-component-init]" \
"complex numbers clang.c:10:6: warning: ISO C does not support '++'/'--' on complex type '_Complex double' [-Wpedantic]" \
"complex numbers clang.c:12:32: error: argument type 'int' is not a real floating point type" \
"complex numbers clang.c:13:32: error: arguments are of different types ('double' vs 'float')" \

0 comments on commit 7fc1f62

Please sign in to comment.