@@ -248,3 +248,127 @@ func sretAttribute(ctx llvm.Context, typ llvm.Type) llvm.Attribute {
248248 id := llvm .AttributeKindID ("sret" )
249249 return ctx .CreateTypeAttribute (id , typ )
250250}
251+
252+ // TestIssue1608_AttrPointerReturnNoMemcpy is a regression test for
253+ // https://github.com/goplus/llgo/issues/1608
254+ //
255+ // When transforming functions with sret (structure return), the optimizer
256+ // previously used memcpy from the load source address. However, if the source
257+ // address content is modified between load and ret, the memcpy would copy the
258+ // wrong value. The fix is to always use store instead of memcpy.
259+ func TestIssue1608_AttrPointerReturnNoMemcpy (t * testing.T ) {
260+ // Create test IR that mimics the bug scenario:
261+ // 1. Load a slice from struct field
262+ // 2. Modify the struct field
263+ // 3. Return the originally loaded slice
264+ testIR := `; ModuleID = 'test'
265+ source_filename = "test"
266+
267+ %Slice = type { ptr, i64, i64 }
268+ %T = type { %Slice }
269+
270+ define %Slice @testfunc() {
271+ entry:
272+ %0 = alloca %T, align 8
273+ %1 = getelementptr inbounds %T, ptr %0, i32 0, i32 0
274+
275+ ; Store first slice {ptr, 2, 2}
276+ %2 = insertvalue %Slice undef, ptr null, 0
277+ %3 = insertvalue %Slice %2, i64 2, 1
278+ %4 = insertvalue %Slice %3, i64 2, 2
279+ store %Slice %4, ptr %1, align 8
280+
281+ ; Load the slice (should return this value)
282+ %5 = getelementptr inbounds %T, ptr %0, i32 0, i32 0
283+ %6 = load %Slice, ptr %5, align 8
284+
285+ ; Modify the source - THIS IS THE KEY!
286+ %7 = insertvalue %Slice undef, ptr null, 0
287+ %8 = insertvalue %Slice %7, i64 3, 1
288+ %9 = insertvalue %Slice %8, i64 3, 2
289+ %10 = getelementptr inbounds %T, ptr %0, i32 0, i32 0
290+ store %Slice %9, ptr %10, align 8
291+
292+ ; Return the originally loaded value
293+ ret %Slice %6
294+ }
295+ `
296+
297+ // Expected IR after CABI transformation:
298+ // - Function signature changed to use sret
299+ // - Return value stored via sret pointer (NOT memcpy!)
300+ // - Returns void
301+ expectedFuncIR := `define void @testfunc(ptr sret(%Slice) %0) {
302+ entry:
303+ %1 = alloca %T, align 8
304+ %2 = getelementptr inbounds %T, ptr %1, i32 0, i32 0
305+ %3 = insertvalue %Slice undef, ptr null, 0
306+ %4 = insertvalue %Slice %3, i64 2, 1
307+ %5 = insertvalue %Slice %4, i64 2, 2
308+ store %Slice %5, ptr %2, align 8
309+ %6 = getelementptr inbounds %T, ptr %1, i32 0, i32 0
310+ %7 = load %Slice, ptr %6, align 8
311+ %8 = insertvalue %Slice undef, ptr null, 0
312+ %9 = insertvalue %Slice %8, i64 3, 1
313+ %10 = insertvalue %Slice %9, i64 3, 2
314+ %11 = getelementptr inbounds %T, ptr %1, i32 0, i32 0
315+ store %Slice %10, ptr %11, align 8
316+ store %Slice %7, ptr %0, align 8
317+ ret void
318+ }
319+ `
320+
321+ ctx := llvm .NewContext ()
322+ defer ctx .Dispose ()
323+
324+ // Write test IR to temporary file
325+ tmpfile := filepath .Join (t .TempDir (), "test.ll" )
326+ if err := os .WriteFile (tmpfile , []byte (testIR ), 0644 ); err != nil {
327+ t .Fatalf ("Failed to write test IR: %v" , err )
328+ }
329+
330+ // Parse the test IR
331+ buf , err := llvm .NewMemoryBufferFromFile (tmpfile )
332+ if err != nil {
333+ t .Fatalf ("Failed to read test IR: %v" , err )
334+ }
335+
336+ mod , err := ctx .ParseIR (buf )
337+ if err != nil {
338+ t .Fatalf ("Failed to parse test IR: %v" , err )
339+ }
340+ defer mod .Dispose ()
341+
342+ // Get the function before transformation
343+ fn := mod .NamedFunction ("testfunc" )
344+ if fn .IsNil () {
345+ t .Fatal ("Test function not found" )
346+ }
347+
348+ // Build minimal config for CABI transformation
349+ conf , _ := buildConf (cabi .ModeAllFunc , "arm64" )
350+ pkgs , err := build .Do ([]string {"./_testdata/demo/demo.go" }, conf )
351+ if err != nil {
352+ t .Fatalf ("Failed to build demo: %v" , err )
353+ }
354+
355+ prog := pkgs [0 ].LPkg .Prog
356+
357+ // Apply CABI transformation with optimize=true
358+ // This tests the code path that previously had the memcpy bug
359+ tr := cabi .NewTransformer (prog , "" , "" , cabi .ModeAllFunc , true )
360+ tr .TransformModule ("test" , mod )
361+
362+ // Get transformed function IR
363+ transformedFn := mod .NamedFunction ("testfunc" )
364+ if transformedFn .IsNil () {
365+ t .Fatal ("Transformed function not found" )
366+ }
367+ actualFuncIR := strings .TrimSpace (transformedFn .String ())
368+ expectedFuncIRTrimmed := strings .TrimSpace (expectedFuncIR )
369+
370+ // Compare IR
371+ if actualFuncIR != expectedFuncIRTrimmed {
372+ t .Errorf ("Transformed IR mismatch!\n \n Expected:\n %s\n \n Actual:\n %s" , expectedFuncIRTrimmed , actualFuncIR )
373+ }
374+ }
0 commit comments