authorgravatar for evan@lagerdata.comEvan Haas <evan@lagerdata.com> 2021-02-22 13:09:51-08:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2021-02-25 22:24:11+02:00
log4f11a88b9f21a365cf2281e840ed12b0d4e687a6
tree2cdfb246283630b20a5ab1753175d4e724f42c3c
parent53cc63f0c91ce9f6f9c81f87e2673c5adfe1afe7

translate-c: Add support for pointer subtraction

When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object; the result is the difference of the subscripts of the two array elements. The size of the result is implementation-defined, and its type (a signed integer type) is ptrdiff_t defined in the <stddef.h> header. If the result is not representable in an object of that type, the behavior is undefined. See C Standard, §6.5.6 [ISO/IEC 9899:2011] Fixes #7216

3 files changed, 143 insertions(+), 2 deletions(-)

src/translate_c.zig+39-2
...@@ -1083,6 +1083,7 @@ fn transBinaryOperator(...@@ -1083,6 +1083,7 @@ fn transBinaryOperator(
1083) TransError!Node {1083) TransError!Node {
1084 const op = stmt.getOpcode();1084 const op = stmt.getOpcode();
1085 const qt = stmt.getType();1085 const qt = stmt.getType();
1086 const isPointerDiffExpr = cIsPointerDiffExpr(c, stmt);
1086 switch (op) {1087 switch (op) {
1087 .Assign => return try transCreateNodeAssign(c, scope, result_used, stmt.getLHS(), stmt.getRHS()),1088 .Assign => return try transCreateNodeAssign(c, scope, result_used, stmt.getLHS(), stmt.getRHS()),
1088 .Comma => {1089 .Comma => {
...@@ -1143,7 +1144,7 @@ fn transBinaryOperator(...@@ -1143,7 +1144,7 @@ fn transBinaryOperator(
1143 }1144 }
1144 },1145 },
1145 .Sub => {1146 .Sub => {
1146 if (cIsUnsignedInteger(qt)) {1147 if (cIsUnsignedInteger(qt) or isPointerDiffExpr) {
1147 op_id = .sub_wrap;1148 op_id = .sub_wrap;
1148 } else {1149 } else {
1149 op_id = .sub;1150 op_id = .sub;
...@@ -1199,15 +1200,40 @@ fn transBinaryOperator(...@@ -1199,15 +1200,40 @@ fn transBinaryOperator(
11991200
1200 const lhs = if (isBoolRes(lhs_uncasted))1201 const lhs = if (isBoolRes(lhs_uncasted))
1201 try Tag.bool_to_int.create(c.arena, lhs_uncasted)1202 try Tag.bool_to_int.create(c.arena, lhs_uncasted)
1203 else if (isPointerDiffExpr)
1204 try Tag.ptr_to_int.create(c.arena, lhs_uncasted)
1202 else1205 else
1203 lhs_uncasted;1206 lhs_uncasted;
12041207
1205 const rhs = if (isBoolRes(rhs_uncasted))1208 const rhs = if (isBoolRes(rhs_uncasted))
1206 try Tag.bool_to_int.create(c.arena, rhs_uncasted)1209 try Tag.bool_to_int.create(c.arena, rhs_uncasted)
1210 else if (isPointerDiffExpr)
1211 try Tag.ptr_to_int.create(c.arena, rhs_uncasted)
1207 else1212 else
1208 rhs_uncasted;1213 rhs_uncasted;
12091214
1210 return transCreateNodeInfixOp(c, scope, op_id, lhs, rhs, result_used);1215 const infixOpNode = try transCreateNodeInfixOp(c, scope, op_id, lhs, rhs, result_used);
1216 if (isPointerDiffExpr) {
1217 // @divExact(@bitCast(<platform-ptrdiff_t>, @ptrToInt(lhs) -% @ptrToInt(rhs)), @sizeOf(<lhs target type>))
1218 const ptrdiff_type = try transQualTypeIntWidthOf(c, qt, true);
1219
1220 // C standard requires that pointer subtraction operands are of the same type,
1221 // otherwise it is undefined behavior. So we can assume the left and right
1222 // sides are the same QualType and arbitrarily choose left.
1223 const lhs_expr = stmt.getLHS();
1224 const lhs_qt = getExprQualType(c, lhs_expr);
1225 const lhs_qt_translated = try transQualType(c, scope, lhs_qt, lhs_expr.getBeginLoc());
1226 const elem_type = lhs_qt_translated.castTag(.c_pointer).?.data.elem_type;
1227 const sizeof = try Tag.sizeof.create(c.arena, elem_type);
1228
1229 const bitcast = try Tag.bit_cast.create(c.arena, .{ .lhs = ptrdiff_type, .rhs = infixOpNode });
1230
1231 return Tag.div_exact.create(c.arena, .{
1232 .lhs = bitcast,
1233 .rhs = sizeof,
1234 });
1235 }
1236 return infixOpNode;
1211}1237}
12121238
1213fn transCompoundStmtInline(1239fn transCompoundStmtInline(
...@@ -1683,6 +1709,17 @@ fn transStringLiteralAsArray(...@@ -1683,6 +1709,17 @@ fn transStringLiteralAsArray(
1683 });1709 });
1684}1710}
16851711
1712/// determine whether `stmt` is a "pointer subtraction expression" - a subtraction where
1713/// both operands resolve to addresses. The C standard requires that both operands
1714/// point to elements of the same array object, but we do not verify that here.
1715fn cIsPointerDiffExpr(c: *Context, stmt: *const clang.BinaryOperator) bool {
1716 const lhs = @ptrCast(*const clang.Stmt, stmt.getLHS());
1717 const rhs = @ptrCast(*const clang.Stmt, stmt.getRHS());
1718 return stmt.getOpcode() == .Sub and
1719 qualTypeIsPtr(@ptrCast(*const clang.Expr, lhs).getType()) and
1720 qualTypeIsPtr(@ptrCast(*const clang.Expr, rhs).getType());
1721}
1722
1686fn cIsEnum(qt: clang.QualType) bool {1723fn cIsEnum(qt: clang.QualType) bool {
1687 return qt.getCanonicalType().getTypeClass() == .Enum;1724 return qt.getCanonicalType().getTypeClass() == .Enum;
1688}1725}
src/translate_c/ast.zig+8
...@@ -146,6 +146,8 @@ pub const Node = extern union {...@@ -146,6 +146,8 @@ pub const Node = extern union {
146 align_cast,146 align_cast,
147 /// @ptrCast(lhs, rhs)147 /// @ptrCast(lhs, rhs)
148 ptr_cast,148 ptr_cast,
149 /// @divExact(lhs, rhs)
150 div_exact,
149151
150 negate,152 negate,
151 negate_wrap,153 negate_wrap,
...@@ -300,6 +302,7 @@ pub const Node = extern union {...@@ -300,6 +302,7 @@ pub const Node = extern union {
300 .array_access,302 .array_access,
301 .std_mem_zeroinit,303 .std_mem_zeroinit,
302 .ptr_cast,304 .ptr_cast,
305 .div_exact,
303 => Payload.BinOp,306 => Payload.BinOp,
304307
305 .integer_literal,308 .integer_literal,
...@@ -1128,6 +1131,10 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {...@@ -1128,6 +1131,10 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
1128 const payload = node.castTag(.ptr_cast).?.data;1131 const payload = node.castTag(.ptr_cast).?.data;
1129 return renderBuiltinCall(c, "@ptrCast", &.{ payload.lhs, payload.rhs });1132 return renderBuiltinCall(c, "@ptrCast", &.{ payload.lhs, payload.rhs });
1130 },1133 },
1134 .div_exact => {
1135 const payload = node.castTag(.div_exact).?.data;
1136 return renderBuiltinCall(c, "@divExact", &.{ payload.lhs, payload.rhs });
1137 },
1131 .sizeof => {1138 .sizeof => {
1132 const payload = node.castTag(.sizeof).?.data;1139 const payload = node.castTag(.sizeof).?.data;
1133 return renderBuiltinCall(c, "@sizeOf", &.{payload});1140 return renderBuiltinCall(c, "@sizeOf", &.{payload});
...@@ -1993,6 +2000,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {...@@ -1993,6 +2000,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
1993 .call,2000 .call,
1994 .array_type,2001 .array_type,
1995 .bool_to_int,2002 .bool_to_int,
2003 .div_exact,
1996 => {2004 => {
1997 // no grouping needed2005 // no grouping needed
1998 return renderNode(c, node);2006 return renderNode(c, node);
test/run_translated_c.zig+96
...@@ -958,4 +958,100 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {...@@ -958,4 +958,100 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
958 \\ return 0;958 \\ return 0;
959 \\}959 \\}
960 , "");960 , "");
961
962 cases.add("pointer difference: scalar array w/ size truncation or negative result. Issue #7216",
963 \\#include <stdlib.h>
964 \\#include <stddef.h>
965 \\#define SIZE 10
966 \\int main() {
967 \\ int foo[SIZE];
968 \\ int *start = &foo[0];
969 \\ int *one_past_end = start + SIZE;
970 \\ ptrdiff_t diff = one_past_end - start;
971 \\ char diff_char = one_past_end - start;
972 \\ if (diff != SIZE || diff_char != SIZE) abort();
973 \\ diff = start - one_past_end;
974 \\ if (diff != -SIZE) abort();
975 \\ if (one_past_end - foo != SIZE) abort();
976 \\ if ((one_past_end - 1) - foo != SIZE - 1) abort();
977 \\ if ((start + 1) - foo != 1) abort();
978 \\ return 0;
979 \\}
980 , "");
981
982 // C standard: if the expression P points either to an element of an array object or one
983 // past the last element of an array object, and the expression Q points to the last
984 // element of the same array object, the expression ((Q)+1)-(P) has the same value as
985 // ((Q)-(P))+1 and as -((P)-((Q)+1)), and has the value zero if the expression P points
986 // one past the last element of the array object, even though the expression (Q)+1
987 // does not point to an element of the array object
988 cases.add("pointer difference: C standard edge case",
989 \\#include <stdlib.h>
990 \\#include <stddef.h>
991 \\#define SIZE 10
992 \\int main() {
993 \\ int foo[SIZE];
994 \\ int *start = &foo[0];
995 \\ int *P = start + SIZE;
996 \\ int *Q = &foo[SIZE - 1];
997 \\ if ((Q + 1) - P != 0) abort();
998 \\ if ((Q + 1) - P != (Q - P) + 1) abort();
999 \\ if ((Q + 1) - P != -(P - (Q + 1))) abort();
1000 \\ return 0;
1001 \\}
1002 , "");
1003
1004 cases.add("pointer difference: unary operators",
1005 \\#include <stdlib.h>
1006 \\int main() {
1007 \\ int foo[10];
1008 \\ int *x = &foo[1];
1009 \\ const int *y = &foo[5];
1010 \\ if (y - x++ != 4) abort();
1011 \\ if (y - x != 3) abort();
1012 \\ if (y - ++x != 2) abort();
1013 \\ if (y - x-- != 2) abort();
1014 \\ if (y - x != 3) abort();
1015 \\ if (y - --x != 4) abort();
1016 \\ if (y - &foo[0] != 5) abort();
1017 \\ return 0;
1018 \\}
1019 , "");
1020
1021 cases.add("pointer difference: struct array with padding",
1022 \\#include <stdlib.h>
1023 \\#include <stddef.h>
1024 \\#define SIZE 10
1025 \\typedef struct my_struct {
1026 \\ int x;
1027 \\ char c;
1028 \\ int y;
1029 \\} my_struct_t;
1030 \\int main() {
1031 \\ my_struct_t foo[SIZE];
1032 \\ my_struct_t *start = &foo[0];
1033 \\ my_struct_t *one_past_end = start + SIZE;
1034 \\ ptrdiff_t diff = one_past_end - start;
1035 \\ int diff_int = one_past_end - start;
1036 \\ if (diff != SIZE || diff_int != SIZE) abort();
1037 \\ diff = start - one_past_end;
1038 \\ if (diff != -SIZE) abort();
1039 \\ return 0;
1040 \\}
1041 , "");
1042
1043 cases.add("pointer difference: array of function pointers",
1044 \\#include <stdlib.h>
1045 \\int a(void) { return 1;}
1046 \\int b(void) { return 2;}
1047 \\int c(void) { return 3;}
1048 \\typedef int (*myfunc)(void);
1049 \\int main() {
1050 \\ myfunc arr[] = {a, b, c, a, b, c};
1051 \\ myfunc *f1 = &arr[1];
1052 \\ myfunc *f4 = &arr[4];
1053 \\ if (f4 - f1 != 3) abort();
1054 \\ return 0;
1055 \\}
1056 , "");
961}1057}