goplus / llgo

A Go compiler based on LLVM in order to better integrate Go with the C ecosystem including Python
Apache License 2.0
362 stars 26 forks source link

Incorrect comparison when setting Slice length with i32 data #848

Closed luoliwoshang closed 3 weeks ago

luoliwoshang commented 3 weeks ago

When setting the length of a Slice using c.Uint data (i32) in LLGo, an incorrect comparison occurs when the length is read and compared later in the code. This leads to unexpected behavior, such as executing loop bodies even when the Slice length is 0. Investigation The issue arises because when setting the Slice length, the c.Uint data is input as i32. However, when reading and comparing the length later, the len (i64) data of the slice is directly read. This type mismatch causes the comparison to fail, leading to incorrect program flow.

func main() {
    var stack *int
    var length c.Uint
    locations := unsafe.Slice(stack, length)
    lens := len(locations)
    c.Printf(c.Str("len: %d\n"), lens)
    c.Printf(c.Str("len > 0: %d\n"), lens > 0)
}

output

len: 0
len > 0: 1

The generated IR code for the relevant part is as follows: In the IR code, we can see that the Slice length is stored as i32 (%9 and %10), but when extracting the length (%12) and performing the comparison (%14), it is treated as i64, leading to the incorrect comparison result.

%7 = alloca %"github.com/goplus/llgo/internal/runtime.Slice", align 8
%8 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %7, i32 0, i32 0
store ptr %1, ptr %8, align 8
%9 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %7, i32 0, i32 1
store i32 %2, ptr %9, align 4
%10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %7, i32 0, i32 2
store i32 %2, ptr %10, align 4
%11 = load %"github.com/goplus/llgo/internal/runtime.Slice", ptr %7, align 8
%12 = extractvalue %"github.com/goplus/llgo/internal/runtime.Slice" %11, 1
%13 = call i32 (ptr, ...) @printf(ptr @2, i64 %12)
%14 = icmp sgt i64 %12, 0

IR Code (uint) However, when using the uint type instead of c.Uint, the generated IR code stores the length as i64:

package main

import (
    "unsafe"

    "math/rand"

    "github.com/goplus/llgo/c"
)

func main() {
    var stack *int
    var length uint
    if rand.Intn(10) > 5 {
        length = 10
    }
    locations := unsafe.Slice(stack, length)
    lens := len(locations)
    c.Printf(c.Str("len: %d\n"), lens)
    c.Printf(c.Str("len > 0: %d\n"), lens > 0)
}
%7 = alloca %"github.com/goplus/llgo/internal/runtime.Slice", align 8
%8 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %7, i32 0, i32 0
store ptr null, ptr %8, align 8
%9 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %7, i32 0, i32 1
store i64 %4, ptr %9, align 4
%10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %7, i32 0, i32 2
store i64 %4, ptr %10, align 4
%11 = load %"github.com/goplus/llgo/internal/runtime.Slice", ptr %7, align 8
%12 = extractvalue %"github.com/goplus/llgo/internal/runtime.Slice" %11, 1
%13 = call i32 (ptr, ...) @printf(ptr @0, i64 %12)
%14 = icmp sgt i64 %12, 0

In this case, the Slice length is stored as i64 (%9 and %10), and the comparison (%14) is performed correctly using i64, avoiding the issue.