diff --git a/amd64_translate.go b/amd64_translate.go index 64ae5abe..c07ab7c5 100644 --- a/amd64_translate.go +++ b/amd64_translate.go @@ -261,9 +261,10 @@ func (c *amd64Ctx) lowerInstr(bi int, ii int, ins Instr, emitBr amd64EmitBr, emi return true, fmt.Errorf("amd64 RET expects at most 1 operand: %q", ins.Raw) } return true, c.lowerRET() - case "PCALIGN", "NO_LOCAL_POINTERS", "PCDATA", "FUNCDATA", "NOP", + case "PCALIGN", "GO_ARGS", "NO_LOCAL_POINTERS", "PCDATA", "FUNCDATA", "NOP", "PUSH_REGS_HOST_TO_ABI0()", "POP_REGS_HOST_TO_ABI0()": - // Alignment directive emitted by stdlib asm; no semantic effect in our IR. + // Go assembler metadata (including GO_ARGS's argument pointer map) has + // no machine-code effect here; LLVM handles the argument values directly. return false, nil case "ADJSP": if c.goarch != "386" { diff --git a/doc/plan9asm-corpus.md b/doc/plan9asm-corpus.md index 80ee4bc5..9abaee17 100644 --- a/doc/plan9asm-corpus.md +++ b/doc/plan9asm-corpus.md @@ -210,7 +210,7 @@ silently include a nested `/v2` module that happens to share its import prefix. The currently tracked reports are: - `xgo-dev/llgo#2464`: `github.com/coder/websocket v1.8.15` -- `xgo-dev/llgo#2552`: `github.com/klauspost/compress v1.20.0` +- `xgo-dev/llgo#2552`: `github.com/klauspost/compress v1.20.1` - `xgo-dev/llgo#2576`: `github.com/tmthrgd/go-hex` at its latest pseudo-version The ecosystem scan also pins these latest modules: @@ -229,7 +229,7 @@ The ecosystem scan also pins these latest modules: - `github.com/klauspost/reedsolomon v1.14.2` - `github.com/minio/highwayhash v1.0.4` - `github.com/modern-go/gls v0.0.0-20250215024828-78308f6bb19d` -- `github.com/pierrec/lz4/v4 v4.1.30` +- `github.com/pierrec/lz4/v4 v4.1.31` - `github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50` - `github.com/tmthrgd/go-bitwise v0.0.0-20190904053232-1430ee983fca` - `github.com/tmthrgd/go-popcount v0.0.0-20190904054823-afb1ace8b04f` diff --git a/go_translate.go b/go_translate.go index 40e781d3..3476599b 100644 --- a/go_translate.go +++ b/go_translate.go @@ -779,6 +779,13 @@ func goLLVMTypeForTypeWithSizes(t types.Type, goarch string, sz types.Sizes) (LL if tt.NumFields() == 0 { return LLVMType("[0 x i8]"), nil } + if parts, ok := goWordStructParts(tt, goarch, sz, sz); ok { + fields := make([]string, len(parts)) + for i, part := range parts { + fields[i] = string(part.Type) + } + return LLVMType("{ " + strings.Join(fields, ", ") + " }"), nil + } return "", fmt.Errorf("unsupported struct type %s", tt.String()) case *types.Named: return goLLVMTypeForTypeWithSizes(tt.Underlying(), goarch, sz) @@ -829,6 +836,9 @@ func goLLVMArgsAndFrameSlotsForTuple(tup *types.Tuple, goarch string, sz, frameS off += int64(frameSz.Sizeof(t)) continue } + if st, ok := t.Underlying().(*types.Struct); ok && st.NumFields() != 0 { + return nil, nil, 0, fmt.Errorf("unsupported struct frame layout %s", t.String()) + } ty, e := goLLVMTypeForTypeWithSizes(t, goarch, sz) if e != nil { @@ -868,10 +878,46 @@ func goFramePartsForTypeWithSizes(t types.Type, goarch string, sz, frameSz types return []goFramePart{{Offset: 0, Type: Ptr, Field: 0}, {Offset: word, Type: wordTy, Field: 1}, {Offset: 2 * word, Type: wordTy, Field: 2}}, true case *types.Interface: return []goFramePart{{Offset: 0, Type: Ptr, Field: 0}, {Offset: word, Type: Ptr, Field: 1}}, true + case *types.Struct: + return goWordStructParts(u, goarch, sz, frameSz) } return nil, false } +// goWordStructParts accepts only flat structures whose fields are exactly one +// integer/pointer word each in both the Go value and the assembly FP frame. +// Other structures need target-specific padding and register classification. +// Even word-sized nested structs are rejected: supporting them would require +// recursive flattening and nested field-index mapping instead of a single Field. +func goWordStructParts(st *types.Struct, goarch string, sz, frameSz types.Sizes) ([]goFramePart, bool) { + word := int64(goWordSizeForSizes(goarch, sz)) + if st.NumFields() == 0 || word != int64(goWordSizeForSizes(goarch, frameSz)) { + return nil, false + } + fields := make([]*types.Var, st.NumFields()) + for i := range fields { + fields[i] = st.Field(i) + } + valueOffsets := sz.Offsetsof(fields) + frameOffsets := frameSz.Offsetsof(fields) + if sz.Sizeof(st) != int64(len(fields))*word || frameSz.Sizeof(st) != int64(len(fields))*word { + return nil, false + } + parts := make([]goFramePart, len(fields)) + for i, field := range fields { + if valueOffsets[i] != int64(i)*word || frameOffsets[i] != int64(i)*word || + sz.Sizeof(field.Type()) != word || frameSz.Sizeof(field.Type()) != word { + return nil, false + } + ty, err := goLLVMTypeForTypeWithSizes(field.Type(), goarch, sz) + if err != nil || (ty != I32 && ty != I64 && ty != Ptr) { + return nil, false + } + parts[i] = goFramePart{Offset: frameOffsets[i], Type: ty, Field: i} + } + return parts, true +} + func goWordSize(goarch string) int { switch goarch { case "amd64", "arm64", "wasm": diff --git a/go_translate_deep_test.go b/go_translate_deep_test.go index a788b26c..8ddda334 100644 --- a/go_translate_deep_test.go +++ b/go_translate_deep_test.go @@ -6,8 +6,11 @@ import ( "go/parser" "go/token" "go/types" + "reflect" "strings" "testing" + + "github.com/xgo-dev/llvm" ) func mustGoPackageWithImports(t *testing.T, pkgPath string, files map[string]string) GoPackage { @@ -135,7 +138,8 @@ func TestGoTranslateTypeCoverage(t *testing.T) { {alias, "arm64", I64, true}, {types.Typ[types.Complex64], "amd64", "", false}, {types.NewStruct(nil, nil), "amd64", LLVMType("[0 x i8]"), true}, - {types.NewStruct([]*types.Var{types.NewVar(token.NoPos, nil, "x", types.Typ[types.Int])}, nil), "amd64", "", false}, + {types.NewStruct([]*types.Var{types.NewVar(token.NoPos, nil, "x", types.Typ[types.Int])}, nil), "amd64", "{ i64 }", true}, + {types.NewStruct([]*types.Var{types.NewVar(token.NoPos, nil, "x", types.Typ[types.Int8])}, nil), "amd64", "", false}, } { got, err := goLLVMTypeForType(tc.typ, tc.goarch) if (err == nil) != tc.ok || got != tc.want { @@ -179,6 +183,93 @@ func TestGoTranslateTypeCoverage(t *testing.T) { } } +func TestGoTranslateWordStructABI(t *testing.T) { + pkg := mustGoPackage(t, "test/pkg", `package testpkg +type Uint128 struct { Lo, Hi uint64 } +func Y(t *byte, a, b Uint128, res uintptr) int32 +func X(t *byte, a, b Uint128, res uintptr) int32 +`) + fn := pkg.Types.Scope().Lookup("Y").(*types.Func) + sz := types.SizesFor("gc", "amd64") + sig, err := goFuncSigForDeclaredFunc("test/pkg.Y", fn, "amd64", sz, sz, true) + if err != nil { + t.Fatal(err) + } + if want := []LLVMType{Ptr, "{ i64, i64 }", "{ i64, i64 }", I64}; !reflect.DeepEqual(sig.Args, want) { + t.Fatalf("args = %v, want %v", sig.Args, want) + } + if want := []FrameSlot{ + {Offset: 0, Type: Ptr, Index: 0, Field: -1}, + {Offset: 8, Type: I64, Index: 1, Field: 0}, + {Offset: 16, Type: I64, Index: 1, Field: 1}, + {Offset: 24, Type: I64, Index: 2, Field: 0}, + {Offset: 32, Type: I64, Index: 2, Field: 1}, + {Offset: 40, Type: I64, Index: 3, Field: -1}, + }; !reflect.DeepEqual(sig.Frame.Params, want) { + t.Fatalf("frame params = %v, want %v", sig.Frame.Params, want) + } + if want := []FrameSlot{{Offset: 48, Type: I32, Index: 0, Field: -1}}; !reflect.DeepEqual(sig.Frame.Results, want) { + t.Fatalf("frame results = %v, want %v", sig.Frame.Results, want) + } + if _, err := goFuncSigForDeclaredFunc("test/pkg.Y", fn, "386", types.SizesFor("gc", "386"), nil, true); err == nil { + t.Fatal("Uint128 must not be flattened into 32-bit frame words") + } + + tr, err := TranslateGoModule(pkg, []byte(`TEXT ·Y(SB),$56-52 +GO_ARGS +NO_LOCAL_POINTERS +MOVQ t+0(FP), AX +MOVQ AX, 0(SP) +MOVQ a_Lo+8(FP), AX +MOVQ AX, 8(SP) +MOVQ a_Hi+16(FP), AX +MOVQ AX, 16(SP) +MOVQ b_Lo+24(FP), AX +MOVQ AX, 24(SP) +MOVQ b_Hi+32(FP), AX +MOVQ AX, 32(SP) +MOVQ res+40(FP), AX +MOVQ AX, 40(SP) +CALL ·X(SB) +MOVL 48(SP), AX +MOVL AX, ret+48(FP) +RET +`), GoModuleOptions{GOARCH: "amd64", TargetTriple: "x86_64-unknown-linux-gnu", ResolveSym: testResolveSym("test/pkg")}) + if err != nil { + t.Fatal(err) + } + defer tr.Module.Dispose() + if err := llvm.VerifyModule(tr.Module, llvm.ReturnStatusAction); err != nil { + t.Fatalf("invalid LLVM IR: %v", err) + } + ir := tr.Module.String() + if !strings.Contains(ir, "extractvalue { i64, i64 } %arg1, 0") || !strings.Contains(ir, "extractvalue { i64, i64 } %arg2, 1") || !strings.Contains(ir, "call i32 @\"test/pkg.X\"") { + t.Fatalf("missing aggregate argument forwarding in LLVM IR:\n%s", ir) + } +} + +func TestGoTranslateWordStructGuards(t *testing.T) { + for _, tc := range []struct { + name, arg, frameArch string + }{ + {"padding", "struct { a uint8; b uint64 }", "amd64"}, + {"subword", "struct { a, b uint32 }", "amd64"}, + {"nested", "struct { inner struct { x uint64 } }", "amd64"}, + {"float", "struct { x float64 }", "amd64"}, + {"frame-word-mismatch", "struct { x uintptr }", "386"}, + } { + t.Run(tc.name, func(t *testing.T) { + pkg := mustGoPackage(t, "test/pkg", "package testpkg\nfunc F(x "+tc.arg+")") + fn := pkg.Types.Scope().Lookup("F").(*types.Func) + sz := types.SizesFor("gc", "amd64") + frameSz := types.SizesFor("gc", tc.frameArch) + if _, err := goFuncSigForDeclaredFunc("test/pkg.F", fn, "amd64", sz, frameSz, true); err == nil || !strings.Contains(err.Error(), "unsupported struct") { + t.Fatalf("expected unsupported struct layout, got %v", err) + } + }) + } +} + func TestGoTranslateSignatureCoverage(t *testing.T) { pkg := mustGoPackage(t, "test/pkg", `package testpkg type S struct{} diff --git a/testdata/corpus/go.mod b/testdata/corpus/go.mod index 3fa2be21..464a57b9 100644 --- a/testdata/corpus/go.mod +++ b/testdata/corpus/go.mod @@ -13,13 +13,13 @@ require ( github.com/dgryski/go-bits v0.0.0-20180113010104-bd8a69a71dc2 github.com/dgryski/go-marvin32 v0.0.0-20240117220238-0d39e8c5a8a9 github.com/golang/snappy v1.0.0 - github.com/klauspost/compress v1.20.0 + github.com/klauspost/compress v1.20.1 github.com/klauspost/cpuid v1.3.1 github.com/klauspost/cpuid/v2 v2.4.0 github.com/klauspost/reedsolomon v1.14.2 github.com/minio/highwayhash v1.0.4 github.com/modern-go/gls v0.0.0-20250215024828-78308f6bb19d - github.com/pierrec/lz4/v4 v4.1.30 + github.com/pierrec/lz4/v4 v4.1.31 github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 github.com/tmthrgd/go-bitwise v0.0.0-20190904053232-1430ee983fca github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc diff --git a/testdata/corpus/go.sum b/testdata/corpus/go.sum index 8e7ff470..50f4fa3f 100644 --- a/testdata/corpus/go.sum +++ b/testdata/corpus/go.sum @@ -25,6 +25,8 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= +github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= @@ -43,6 +45,8 @@ github.com/pierrec/lz4/v4 v4.1.29 h1:CDQY6qZOLI4DW0Nx6R1vRrifrCeQHnNXkMb0hZWXFjg github.com/pierrec/lz4/v4 v4.1.29/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pierrec/lz4/v4 v4.1.30 h1:cchX8N2DVP668WkElI9QMwVyoNabLkq1LofDHFeIrdg= github.com/pierrec/lz4/v4 v4.1.30/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pierrec/lz4/v4 v4.1.31 h1:TI8ck6XSudzSzotzAmy0+kh/KpRHaVsKLPzS97gRyNg= +github.com/pierrec/lz4/v4 v4.1.31/go.mod h1:7SE9MC2STkNtL4PIwGhjmyVwvILaGI9/COYQNBhKM/c= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= diff --git a/testdata/corpus/reported-libraries.json b/testdata/corpus/reported-libraries.json index b11dcad4..db4dc55f 100644 --- a/testdata/corpus/reported-libraries.json +++ b/testdata/corpus/reported-libraries.json @@ -56,7 +56,7 @@ "https://github.com/xgo-dev/llgo/issues/2552" ], "module": "github.com/klauspost/compress", - "version": "v1.20.0", + "version": "v1.20.1", "inventory": { "darwin/amd64": { "asm_files": 8, @@ -360,11 +360,14 @@ "id": "pierrec-lz4-v4", "origin": "ecosystem-scan", "module": "github.com/pierrec/lz4/v4", - "version": "v4.1.30", + "version": "v4.1.31", "inventory": { "darwin/amd64": { - "asm_files": 1, - "packages": ["github.com/pierrec/lz4/v4/internal/lz4block"] + "asm_files": 2, + "packages": [ + "github.com/pierrec/lz4/v4/internal/lz4block", + "github.com/pierrec/lz4/v4/internal/xxh32" + ] }, "darwin/arm64": { "asm_files": 2, @@ -374,8 +377,11 @@ ] }, "linux/amd64": { - "asm_files": 1, - "packages": ["github.com/pierrec/lz4/v4/internal/lz4block"] + "asm_files": 2, + "packages": [ + "github.com/pierrec/lz4/v4/internal/lz4block", + "github.com/pierrec/lz4/v4/internal/xxh32" + ] }, "linux/arm": { "asm_files": 2, @@ -392,8 +398,11 @@ ] }, "windows/amd64": { - "asm_files": 1, - "packages": ["github.com/pierrec/lz4/v4/internal/lz4block"] + "asm_files": 2, + "packages": [ + "github.com/pierrec/lz4/v4/internal/lz4block", + "github.com/pierrec/lz4/v4/internal/xxh32" + ] }, "windows/arm64": { "asm_files": 2,