Skip to content

Commit 52bb718

Browse files
committed
implement vector negation
also fix vector behavior tests, they weren't actually testing runtime vectors, but now they are. See #903
1 parent 2fe8a08 commit 52bb718

File tree

5 files changed

+157
-47
lines changed

5 files changed

+157
-47
lines changed

src/codegen.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3229,7 +3229,8 @@ static LLVMValueRef ir_render_br(CodeGen *g, IrExecutable *executable, IrInstruc
32293229
static LLVMValueRef ir_render_un_op(CodeGen *g, IrExecutable *executable, IrInstructionUnOp *un_op_instruction) {
32303230
IrUnOp op_id = un_op_instruction->op_id;
32313231
LLVMValueRef expr = ir_llvm_value(g, un_op_instruction->value);
3232-
ZigType *expr_type = un_op_instruction->value->value.type;
3232+
ZigType *operand_type = un_op_instruction->value->value.type;
3233+
ZigType *scalar_type = (operand_type->id == ZigTypeIdVector) ? operand_type->data.vector.elem_type : operand_type;
32333234

32343235
switch (op_id) {
32353236
case IrUnOpInvalid:
@@ -3239,16 +3240,16 @@ static LLVMValueRef ir_render_un_op(CodeGen *g, IrExecutable *executable, IrInst
32393240
case IrUnOpNegation:
32403241
case IrUnOpNegationWrap:
32413242
{
3242-
if (expr_type->id == ZigTypeIdFloat) {
3243+
if (scalar_type->id == ZigTypeIdFloat) {
32433244
ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &un_op_instruction->base));
32443245
return LLVMBuildFNeg(g->builder, expr, "");
3245-
} else if (expr_type->id == ZigTypeIdInt) {
3246+
} else if (scalar_type->id == ZigTypeIdInt) {
32463247
if (op_id == IrUnOpNegationWrap) {
32473248
return LLVMBuildNeg(g->builder, expr, "");
32483249
} else if (ir_want_runtime_safety(g, &un_op_instruction->base)) {
32493250
LLVMValueRef zero = LLVMConstNull(LLVMTypeOf(expr));
3250-
return gen_overflow_op(g, expr_type, AddSubMulSub, zero, expr);
3251-
} else if (expr_type->data.integral.is_signed) {
3251+
return gen_overflow_op(g, operand_type, AddSubMulSub, zero, expr);
3252+
} else if (scalar_type->data.integral.is_signed) {
32523253
return LLVMBuildNSWNeg(g->builder, expr, "");
32533254
} else {
32543255
return LLVMBuildNUWNeg(g->builder, expr, "");

src/ir.cpp

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14620,6 +14620,41 @@ static IrInstruction *ir_analyze_maybe(IrAnalyze *ira, IrInstructionUnOp *un_op_
1462014620
zig_unreachable();
1462114621
}
1462214622

14623+
static ErrorMsg *ir_eval_negation_scalar(IrAnalyze *ira, IrInstruction *source_instr, ZigType *scalar_type,
14624+
ConstExprValue *operand_val, ConstExprValue *scalar_out_val, bool is_wrap_op)
14625+
{
14626+
bool is_float = (scalar_type->id == ZigTypeIdFloat || scalar_type->id == ZigTypeIdComptimeFloat);
14627+
14628+
bool ok_type = ((scalar_type->id == ZigTypeIdInt && scalar_type->data.integral.is_signed) ||
14629+
scalar_type->id == ZigTypeIdComptimeInt || (is_float && !is_wrap_op));
14630+
14631+
if (!ok_type) {
14632+
const char *fmt = is_wrap_op ? "invalid wrapping negation type: '%s'" : "invalid negation type: '%s'";
14633+
return ir_add_error(ira, source_instr, buf_sprintf(fmt, buf_ptr(&scalar_type->name)));
14634+
}
14635+
14636+
if (is_float) {
14637+
float_negate(scalar_out_val, operand_val);
14638+
} else if (is_wrap_op) {
14639+
bigint_negate_wrap(&scalar_out_val->data.x_bigint, &operand_val->data.x_bigint,
14640+
scalar_type->data.integral.bit_count);
14641+
} else {
14642+
bigint_negate(&scalar_out_val->data.x_bigint, &operand_val->data.x_bigint);
14643+
}
14644+
14645+
scalar_out_val->type = scalar_type;
14646+
scalar_out_val->special = ConstValSpecialStatic;
14647+
14648+
if (is_wrap_op || is_float || scalar_type->id == ZigTypeIdComptimeInt) {
14649+
return nullptr;
14650+
}
14651+
14652+
if (!bigint_fits_in_bits(&scalar_out_val->data.x_bigint, scalar_type->data.integral.bit_count, true)) {
14653+
return ir_add_error(ira, source_instr, buf_sprintf("negation caused overflow"));
14654+
}
14655+
return nullptr;
14656+
}
14657+
1462314658
static IrInstruction *ir_analyze_negation(IrAnalyze *ira, IrInstructionUnOp *instruction) {
1462414659
IrInstruction *value = instruction->value->child;
1462514660
ZigType *expr_type = value->value.type;
@@ -14628,47 +14663,50 @@ static IrInstruction *ir_analyze_negation(IrAnalyze *ira, IrInstructionUnOp *ins
1462814663

1462914664
bool is_wrap_op = (instruction->op_id == IrUnOpNegationWrap);
1463014665

14631-
bool is_float = (expr_type->id == ZigTypeIdFloat || expr_type->id == ZigTypeIdComptimeFloat);
14666+
ZigType *scalar_type = (expr_type->id == ZigTypeIdVector) ? expr_type->data.vector.elem_type : expr_type;
1463214667

14633-
if ((expr_type->id == ZigTypeIdInt && expr_type->data.integral.is_signed) ||
14634-
expr_type->id == ZigTypeIdComptimeInt || (is_float && !is_wrap_op))
14635-
{
14636-
if (instr_is_comptime(value)) {
14637-
ConstExprValue *target_const_val = ir_resolve_const(ira, value, UndefBad);
14638-
if (!target_const_val)
14639-
return ira->codegen->invalid_instruction;
14668+
if (instr_is_comptime(value)) {
14669+
ConstExprValue *operand_val = ir_resolve_const(ira, value, UndefBad);
14670+
if (!operand_val)
14671+
return ira->codegen->invalid_instruction;
1464014672

14641-
IrInstruction *result = ir_const(ira, &instruction->base, expr_type);
14642-
ConstExprValue *out_val = &result->value;
14643-
if (is_float) {
14644-
float_negate(out_val, target_const_val);
14645-
} else if (is_wrap_op) {
14646-
bigint_negate_wrap(&out_val->data.x_bigint, &target_const_val->data.x_bigint,
14647-
expr_type->data.integral.bit_count);
14648-
} else {
14649-
bigint_negate(&out_val->data.x_bigint, &target_const_val->data.x_bigint);
14650-
}
14651-
if (is_wrap_op || is_float || expr_type->id == ZigTypeIdComptimeInt) {
14652-
return result;
14673+
IrInstruction *result_instruction = ir_const(ira, &instruction->base, expr_type);
14674+
ConstExprValue *out_val = &result_instruction->value;
14675+
if (expr_type->id == ZigTypeIdVector) {
14676+
expand_undef_array(ira->codegen, operand_val);
14677+
out_val->special = ConstValSpecialUndef;
14678+
expand_undef_array(ira->codegen, out_val);
14679+
size_t len = expr_type->data.vector.len;
14680+
for (size_t i = 0; i < len; i += 1) {
14681+
ConstExprValue *scalar_operand_val = &operand_val->data.x_array.data.s_none.elements[i];
14682+
ConstExprValue *scalar_out_val = &out_val->data.x_array.data.s_none.elements[i];
14683+
assert(scalar_operand_val->type == scalar_type);
14684+
assert(scalar_out_val->type == scalar_type);
14685+
ErrorMsg *msg = ir_eval_negation_scalar(ira, &instruction->base, scalar_type,
14686+
scalar_operand_val, scalar_out_val, is_wrap_op);
14687+
if (msg != nullptr) {
14688+
add_error_note(ira->codegen, msg, instruction->base.source_node,
14689+
buf_sprintf("when computing vector element at index %" ZIG_PRI_usize, i));
14690+
return ira->codegen->invalid_instruction;
14691+
}
1465314692
}
14654-
14655-
if (!bigint_fits_in_bits(&out_val->data.x_bigint, expr_type->data.integral.bit_count, true)) {
14656-
ir_add_error(ira, &instruction->base, buf_sprintf("negation caused overflow"));
14693+
out_val->type = expr_type;
14694+
out_val->special = ConstValSpecialStatic;
14695+
} else {
14696+
if (ir_eval_negation_scalar(ira, &instruction->base, scalar_type, operand_val, out_val,
14697+
is_wrap_op) != nullptr)
14698+
{
1465714699
return ira->codegen->invalid_instruction;
1465814700
}
14659-
return result;
1466014701
}
14661-
14662-
IrInstruction *result = ir_build_un_op(&ira->new_irb,
14663-
instruction->base.scope, instruction->base.source_node,
14664-
instruction->op_id, value);
14665-
result->value.type = expr_type;
14666-
return result;
14702+
return result_instruction;
1466714703
}
1466814704

14669-
const char *fmt = is_wrap_op ? "invalid wrapping negation type: '%s'" : "invalid negation type: '%s'";
14670-
ir_add_error(ira, &instruction->base, buf_sprintf(fmt, buf_ptr(&expr_type->name)));
14671-
return ira->codegen->invalid_instruction;
14705+
IrInstruction *result = ir_build_un_op(&ira->new_irb,
14706+
instruction->base.scope, instruction->base.source_node,
14707+
instruction->op_id, value);
14708+
result->value.type = expr_type;
14709+
return result;
1467214710
}
1467314711

1467414712
static IrInstruction *ir_analyze_bin_not(IrAnalyze *ira, IrInstructionUnOp *instruction) {

test/compile_errors.zig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
const tests = @import("tests.zig");
22

33
pub fn addCases(cases: *tests.CompileErrorContext) void {
4+
cases.addTest(
5+
"comptime vector overflow shows the index",
6+
\\comptime {
7+
\\ var a: @Vector(4, u8) = []u8{ 1, 2, 255, 4 };
8+
\\ var b: @Vector(4, u8) = []u8{ 5, 6, 1, 8 };
9+
\\ var x = a + b;
10+
\\}
11+
,
12+
".tmp_source.zig:4:15: error: operation caused overflow",
13+
".tmp_source.zig:4:15: note: when computing vector element at index 2",
14+
);
15+
416
cases.addTest(
517
"packed struct with fields of not allowed types",
618
\\const A = packed struct {

test/runtime_safety.zig

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,47 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
118118
\\}
119119
);
120120

121+
cases.addRuntimeSafety("vector integer subtraction overflow",
122+
\\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
123+
\\ @import("std").os.exit(126);
124+
\\}
125+
\\pub fn main() void {
126+
\\ var a: @Vector(4, u32) = []u32{ 1, 2, 8, 4 };
127+
\\ var b: @Vector(4, u32) = []u32{ 5, 6, 7, 8 };
128+
\\ const x = sub(b, a);
129+
\\}
130+
\\fn sub(a: @Vector(4, u32), b: @Vector(4, u32)) @Vector(4, u32) {
131+
\\ return a - b;
132+
\\}
133+
);
134+
135+
cases.addRuntimeSafety("vector integer multiplication overflow",
136+
\\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
137+
\\ @import("std").os.exit(126);
138+
\\}
139+
\\pub fn main() void {
140+
\\ var a: @Vector(4, u8) = []u8{ 1, 2, 200, 4 };
141+
\\ var b: @Vector(4, u8) = []u8{ 5, 6, 2, 8 };
142+
\\ const x = mul(b, a);
143+
\\}
144+
\\fn mul(a: @Vector(4, u8), b: @Vector(4, u8)) @Vector(4, u8) {
145+
\\ return a * b;
146+
\\}
147+
);
148+
149+
cases.addRuntimeSafety("vector integer negation overflow",
150+
\\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
151+
\\ @import("std").os.exit(126);
152+
\\}
153+
\\pub fn main() void {
154+
\\ var a: @Vector(4, i16) = []i16{ 1, -32768, 200, 4 };
155+
\\ const x = neg(a);
156+
\\}
157+
\\fn neg(a: @Vector(4, i16)) @Vector(4, i16) {
158+
\\ return -a;
159+
\\}
160+
);
161+
121162
cases.addRuntimeSafety("integer subtraction overflow",
122163
\\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
123164
\\ @import("std").os.exit(126);

test/stage1/behavior/vector.zig

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,28 @@ const expect = std.testing.expect;
55
test "vector wrap operators" {
66
const S = struct {
77
fn doTheTest() void {
8-
const v: @Vector(4, i32) = [4]i32{ 10, 20, 30, 40 };
9-
const x: @Vector(4, i32) = [4]i32{ 1, 2, 3, 4 };
10-
expect(mem.eql(i32, ([4]i32)(v +% x), [4]i32{ 11, 22, 33, 44 }));
11-
expect(mem.eql(i32, ([4]i32)(v -% x), [4]i32{ 9, 18, 27, 36 }));
12-
expect(mem.eql(i32, ([4]i32)(v *% x), [4]i32{ 10, 40, 90, 160 }));
8+
var v: @Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };
9+
var x: @Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 };
10+
expect(mem.eql(i32, ([4]i32)(v +% x), [4]i32{ -2147483648, 2147483645, 33, 44 }));
11+
expect(mem.eql(i32, ([4]i32)(v -% x), [4]i32{ 2147483646, 2147483647, 27, 36 }));
12+
expect(mem.eql(i32, ([4]i32)(v *% x), [4]i32{ 2147483647, 2, 90, 160 }));
13+
var z: @Vector(4, i32) = [4]i32{ 1, 2, 3, -2147483648 };
14+
expect(mem.eql(i32, ([4]i32)(-%z), [4]i32{ -1, -2, -3, -2147483648 }));
15+
}
16+
};
17+
S.doTheTest();
18+
comptime S.doTheTest();
19+
}
20+
21+
test "vector int operators" {
22+
const S = struct {
23+
fn doTheTest() void {
24+
var v: @Vector(4, i32) = [4]i32{ 10, 20, 30, 40 };
25+
var x: @Vector(4, i32) = [4]i32{ 1, 2, 3, 4 };
26+
expect(mem.eql(i32, ([4]i32)(v + x), [4]i32{ 11, 22, 33, 44 }));
27+
expect(mem.eql(i32, ([4]i32)(v - x), [4]i32{ 9, 18, 27, 36 }));
28+
expect(mem.eql(i32, ([4]i32)(v * x), [4]i32{ 10, 40, 90, 160 }));
29+
expect(mem.eql(i32, ([4]i32)(-v), [4]i32{ -10, -20, -30, -40 }));
1330
}
1431
};
1532
S.doTheTest();
@@ -19,11 +36,12 @@ test "vector wrap operators" {
1936
test "vector float operators" {
2037
const S = struct {
2138
fn doTheTest() void {
22-
const v: @Vector(4, f32) = [4]f32{ 10, 20, 30, 40 };
23-
const x: @Vector(4, f32) = [4]f32{ 1, 2, 3, 4 };
39+
var v: @Vector(4, f32) = [4]f32{ 10, 20, 30, 40 };
40+
var x: @Vector(4, f32) = [4]f32{ 1, 2, 3, 4 };
2441
expect(mem.eql(f32, ([4]f32)(v + x), [4]f32{ 11, 22, 33, 44 }));
2542
expect(mem.eql(f32, ([4]f32)(v - x), [4]f32{ 9, 18, 27, 36 }));
2643
expect(mem.eql(f32, ([4]f32)(v * x), [4]f32{ 10, 40, 90, 160 }));
44+
expect(mem.eql(f32, ([4]f32)(-x), [4]f32{ -1, -2, -3, -4 }));
2745
}
2846
};
2947
S.doTheTest();
@@ -33,8 +51,8 @@ test "vector float operators" {
3351
test "vector bit operators" {
3452
const S = struct {
3553
fn doTheTest() void {
36-
const v: @Vector(4, u8) = [4]u8{ 0b10101010, 0b10101010, 0b10101010, 0b10101010 };
37-
const x: @Vector(4, u8) = [4]u8{ 0b11110000, 0b00001111, 0b10101010, 0b01010101 };
54+
var v: @Vector(4, u8) = [4]u8{ 0b10101010, 0b10101010, 0b10101010, 0b10101010 };
55+
var x: @Vector(4, u8) = [4]u8{ 0b11110000, 0b00001111, 0b10101010, 0b01010101 };
3856
expect(mem.eql(u8, ([4]u8)(v ^ x), [4]u8{ 0b01011010, 0b10100101, 0b00000000, 0b11111111 }));
3957
expect(mem.eql(u8, ([4]u8)(v | x), [4]u8{ 0b11111010, 0b10101111, 0b10101010, 0b11111111 }));
4058
expect(mem.eql(u8, ([4]u8)(v & x), [4]u8{ 0b10100000, 0b00001010, 0b10101010, 0b00000000 }));

0 commit comments

Comments
 (0)