-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
@divFloor returns incorrect value #2152
Comments
Thanks for the report. I'll note that the zig compiler itself agrees with this bug report, because if you wrap the test in a const std = @import("std");
const T = std.testing;
const ModResult = struct {
quotient: i32,
modulus: i32,
};
fn modulus(dividend: i32, divisor: i32) ModResult {
return ModResult{
.quotient = @divFloor(dividend, divisor),
.modulus = @mod(dividend, divisor),
};
}
test "modulus oops" {
comptime {
var mod = modulus(10, 12);
T.expectEqual(i32(10), mod.modulus);
T.expectEqual(i32(0), mod.quotient);
mod = modulus(-14, 12);
T.expectEqual(i32(10), mod.modulus);
T.expectEqual(i32(-2), mod.quotient);
mod = modulus(-2, 12);
T.expectEqual(i32(10), mod.modulus);
T.expectEqual(i32(-1), mod.quotient);
}
} One way to debug this is to inspect the LLVM IR. To get small output we can simplify the input code: export fn entry() void {
var dividend: i32 = -2;
var divisor: i32 = 12;
var result = @divFloor(dividend, divisor);
}
const builtin = @import("builtin");
pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn {
while (true) {}
} and then build with ; ModuleID = 'test'
source_filename = "test"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
%"[]u8" = type { i8*, i64 }
%builtin.StackTrace = type { i64, %"[]usize" }
%"[]usize" = type { i64*, i64 }
@0 = internal unnamed_addr constant [16 x i8] c"division by zero", align 1
@1 = internal unnamed_addr constant %"[]u8" { i8* getelementptr inbounds ([16 x i8], [16 x i8]* @0, i64 0, i64 0), i64 16 }, align 8
@2 = internal unnamed_addr constant [16 x i8] c"integer overflow", align 1
@3 = internal unnamed_addr constant %"[]u8" { i8* getelementptr inbounds ([16 x i8], [16 x i8]* @2, i64 0, i64 0), i64 16 }, align 8
; Function Attrs: nobuiltin noreturn nounwind
define internal fastcc void @panic(%"[]u8"* nonnull readonly align 8, %builtin.StackTrace* align 8) unnamed_addr #0 !dbg !8 {
Entry:
%error_return_trace = alloca %builtin.StackTrace*, align 8
call void @llvm.dbg.declare(metadata %"[]u8"* %0, metadata !33, metadata !DIExpression()), !dbg !36
store %builtin.StackTrace* %1, %builtin.StackTrace** %error_return_trace, align 8
call void @llvm.dbg.declare(metadata %builtin.StackTrace** %error_return_trace, metadata !34, metadata !DIExpression()), !dbg !37
br label %WhileCond, !dbg !38
WhileCond: ; preds = %WhileCond, %Entry
br label %WhileCond, !dbg !38
}
; Function Attrs: nounwind readnone speculatable
declare void @llvm.dbg.declare(metadata, metadata, metadata) #1
; Function Attrs: nobuiltin nounwind
define void @entry() #2 !dbg !41 {
Entry:
%dividend = alloca i32, align 4
%divisor = alloca i32, align 4
%result = alloca i32, align 4
store i32 -2, i32* %dividend, align 4, !dbg !52
call void @llvm.dbg.declare(metadata i32* %dividend, metadata !45, metadata !DIExpression()), !dbg !52
store i32 12, i32* %divisor, align 4, !dbg !53
call void @llvm.dbg.declare(metadata i32* %divisor, metadata !48, metadata !DIExpression()), !dbg !53
%0 = load i32, i32* %dividend, align 4, !dbg !54
%1 = load i32, i32* %divisor, align 4, !dbg !55
%2 = icmp eq i32 %1, 0, !dbg !56
br i1 %2, label %DivZeroFail, label %DivZeroOk, !dbg !56
DivZeroOk: ; preds = %Entry
%3 = icmp eq i32 %0, -2147483648, !dbg !56
%4 = icmp eq i32 %1, -1, !dbg !56
%5 = and i1 %3, %4, !dbg !56
br i1 %5, label %DivOverflowFail, label %DivOverflowOk, !dbg !56
DivZeroFail: ; preds = %Entry
tail call fastcc void @panic(%"[]u8"* @1, %builtin.StackTrace* null), !dbg !56
unreachable, !dbg !56
DivOverflowOk: ; preds = %DivZeroOk
%6 = sdiv i32 %0, %1, !dbg !56
%7 = icmp sge i32 %6, 0, !dbg !56
%8 = mul nsw i32 %6, %1, !dbg !56
%9 = icmp eq i32 %8, %0, !dbg !56
%10 = or i1 %9, %7, !dbg !56
%11 = sub nsw i32 %6, 1, !dbg !56
%12 = select i1 %10, i32 %6, i32 %11, !dbg !56
store i32 %12, i32* %result, align 4, !dbg !57
call void @llvm.dbg.declare(metadata i32* %result, metadata !50, metadata !DIExpression()), !dbg !57
ret void, !dbg !58
DivOverflowFail: ; preds = %DivZeroOk
tail call fastcc void @panic(%"[]u8"* @3, %builtin.StackTrace* null), !dbg !56
unreachable, !dbg !56
}
attributes #0 = { nobuiltin noreturn nounwind "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" }
attributes #1 = { nounwind readnone speculatable }
attributes #2 = { nobuiltin nounwind "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" }
!llvm.module.flags = !{!0}
!llvm.dbg.cu = !{!1}
!0 = !{i32 2, !"Debug Info Version", i32 3}
!1 = distinct !DICompileUnit(language: DW_LANG_C99, file: !2, producer: "zig 0.3.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !3)
!2 = !DIFile(filename: "test", directory: ".")
!3 = !{!4}
!4 = !DICompositeType(tag: DW_TAG_enumeration_type, name: "anyerror", baseType: !5, size: 16, align: 16, elements: !6)
!5 = !DIBasicType(name: "u16", size: 16, encoding: DW_ATE_unsigned)
!6 = !{!7}
!7 = !DIEnumerator(name: "(none)", value: 0)
!8 = distinct !DISubprogram(name: "panic", scope: !9, file: !9, line: 8, type: !10, scopeLine: 8, flags: DIFlagStaticMember, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition, unit: !1, retainedNodes: !32)
!9 = !DIFile(filename: "test.zig", directory: "/home/andy/downloads/zig/build")
!10 = !DISubroutineType(types: !11)
!11 = !{!12, !13, !21}
!12 = !DIBasicType(name: "void", encoding: DW_ATE_unsigned)
!13 = !DIDerivedType(tag: DW_TAG_pointer_type, name: "*[]const u8", baseType: !14, size: 64, align: 64)
!14 = !DICompositeType(tag: DW_TAG_structure_type, name: "[]u8", size: 128, align: 64, elements: !15)
!15 = !{!16, !19}
!16 = !DIDerivedType(tag: DW_TAG_member, name: "ptr", scope: !14, baseType: !17, size: 64, align: 64)
!17 = !DIDerivedType(tag: DW_TAG_pointer_type, name: "*u8", baseType: !18, size: 64, align: 64)
!18 = !DIBasicType(name: "u8", size: 8, encoding: DW_ATE_unsigned_char)
!19 = !DIDerivedType(tag: DW_TAG_member, name: "len", scope: !14, baseType: !20, size: 64, align: 64, offset: 64)
!20 = !DIBasicType(name: "usize", size: 64, encoding: DW_ATE_unsigned)
!21 = !DIDerivedType(tag: DW_TAG_pointer_type, name: "*builtin.StackTrace", baseType: !22, size: 64, align: 64)
!22 = !DICompositeType(tag: DW_TAG_structure_type, name: "builtin.StackTrace", scope: !23, file: !23, line: 1, size: 192, align: 192, elements: !24)
!23 = !DIFile(filename: "builtin.zig", directory: "/home/andy/.local/share/zig/stage1/builtin/0mPg7dcdbGiI_WeJIw9TIOJTbnKQ0RGGgw636R7ctlnPEIalCy2JiUnpeTCdjuP_")
!24 = !{!25, !26}
!25 = !DIDerivedType(tag: DW_TAG_member, name: "index", scope: !22, file: !23, line: 2, baseType: !20, size: 64, align: 64)
!26 = !DIDerivedType(tag: DW_TAG_member, name: "instruction_addresses", scope: !22, file: !23, line: 3, baseType: !27, size: 128, align: 128, offset: 64)
!27 = !DICompositeType(tag: DW_TAG_structure_type, name: "[]usize", size: 128, align: 64, elements: !28)
!28 = !{!29, !31}
!29 = !DIDerivedType(tag: DW_TAG_member, name: "ptr", scope: !27, baseType: !30, size: 64, align: 64)
!30 = !DIDerivedType(tag: DW_TAG_pointer_type, name: "*usize", baseType: !20, size: 64, align: 64)
!31 = !DIDerivedType(tag: DW_TAG_member, name: "len", scope: !27, baseType: !20, size: 64, align: 64, offset: 64)
!32 = !{!33, !34}
!33 = !DILocalVariable(name: "msg", arg: 1, scope: !8, file: !9, line: 8, type: !14)
!34 = !DILocalVariable(name: "error_return_trace", arg: 2, scope: !35, file: !9, line: 8, type: !21)
!35 = distinct !DILexicalBlock(scope: !8, file: !9, line: 8, column: 14)
!36 = !DILocation(line: 8, column: 14, scope: !8)
!37 = !DILocation(line: 8, column: 31, scope: !35)
!38 = !DILocation(line: 9, column: 5, scope: !39)
!39 = distinct !DILexicalBlock(scope: !40, file: !9, line: 8, column: 82)
!40 = distinct !DILexicalBlock(scope: !35, file: !9, line: 8, column: 31)
!41 = distinct !DISubprogram(name: "entry", scope: !9, file: !9, type: !42, flags: DIFlagStaticMember, spFlags: DISPFlagDefinition, unit: !1, retainedNodes: !44)
!42 = !DISubroutineType(types: !43)
!43 = !{!12}
!44 = !{!45, !48, !50}
!45 = !DILocalVariable(name: "dividend", scope: !46, file: !9, line: 2, type: !47)
!46 = distinct !DILexicalBlock(scope: !41, file: !9, line: 1, column: 24)
!47 = !DIBasicType(name: "i32", size: 32, encoding: DW_ATE_signed)
!48 = !DILocalVariable(name: "divisor", scope: !49, file: !9, line: 3, type: !47)
!49 = distinct !DILexicalBlock(scope: !46, file: !9, line: 2, column: 5)
!50 = !DILocalVariable(name: "result", scope: !51, file: !9, line: 4, type: !47)
!51 = distinct !DILexicalBlock(scope: !49, file: !9, line: 3, column: 5)
!52 = !DILocation(line: 2, column: 5, scope: !46)
!53 = !DILocation(line: 3, column: 5, scope: !49)
!54 = !DILocation(line: 4, column: 28, scope: !51)
!55 = !DILocation(line: 4, column: 38, scope: !51)
!56 = !DILocation(line: 4, column: 18, scope: !51)
!57 = !DILocation(line: 4, column: 5, scope: !51)
!58 = !DILocation(line: 1, column: 24, scope: !41) The language reference is here: http://llvm.org/docs/LangRef.html This code is generated here: Lines 2564 to 2582 in aa794eb
|
I wish I could read LLVM (I will follow the links and try to get up to speed); however, I can see a problem in the algorithm, expressed in the commented zig code in lines 2568-2572. I had some code (written in C) to do this, so I ported it. One of the objectives I had when I wrote the original C was to avoid repeating the "expensive" IDIV operations, so that is why I wanted a function to return both. Also, I needed to handle cases where the denominator is negative also; and it appears to be a non-requirement for
and my results:
|
Note that
Last time I looked into this, the optimizer was able to turn separate division and modulus operations into a single hardware instruction. I haven't tried for Thank you for the fixed logic. Were you intending to open a pull request, or is it ok if I push a fix with the new logic? By the way, do any of those operations depend on twos complement wraparound? |
Is the Optimizers are magic to mere mortals like myself, but I have seen them collapse the two IDIV operations in my code down to one, so I believe you! I haven't attempted to make an actual LLVM code fix yet, but I am planning to do so. In the meantime, I just pushed my Gregorian date code to github.com/brhamon/zigdate. I used my Thanks for the amazing work so far. I think you're really onto something with this zig thing. |
I'm not sure I understand your question. The reasoning behind disallowing negative denominators in the Zig language is that there's not really a good use case for having them, and there's not really a good mathematical definition of what should happen. So by restricting the allowed input, we can have better code generated and catch more bugs. |
in github.com/brhamon/zigdate, I've generalized and added test cases for truncatedDiv, flooredDiv and euclideanDiv; and have unit tests for all three. I plan to use generics to use this implementation to handle all bit widths of signed integers. (For unsigned bit width integers, all three types of division operate exactly as truncatedDiv.) I'll make that change shortly. In that code, I ran into an issue with |
If zig segfaults, it's always a compiler bug. |
I'm using zig version 0.3.0+d494a5f4. I'm really impressed with the compiler and the language, but I believe I have found a problem in @divFloor. The following test code should illustrate.
It appears that @divFloor will return the incorrect value for the case shown, using i32 integers on Windows 10 (Build 17134).
Here are my results:
The first five
expectEqual
statements passed, so the only issue appears to be with@divFloor
when the numerator is in the range(-denominator, 0)
. I will pry into this with the debugger and see if I can find the root cause.The text was updated successfully, but these errors were encountered: