From fb377b4978d8b4021f42a714bcdfa9f5e98b9ace Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sat, 1 Aug 2026 19:49:23 +0800 Subject: [PATCH] cmd/compile: preserve newproc pointer argument in LLVM IR --- src/cmd/compile/internal/ssa/ssa2llvm.go | 24 +++++++++++- test/codegen/llvm_newproc.go | 18 +++++++++ test/llvm_newproc_gc.go | 49 ++++++++++++++++++++++++ test/llvm_tests.json | 2 + 4 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 test/codegen/llvm_newproc.go create mode 100644 test/llvm_newproc_gc.go diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index 9238b5eb2738d4..94d99969666956 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -550,6 +550,28 @@ func (lfc *LLVMFuncContext) aggregate(v *Value, args []*Value) llvm.Value { return result } +// llvmNewprocSignature restores the semantic pointer type of newproc's +// funcval argument. Native ssagen intentionally uses uintptr only to compute +// the raw call's physical ABI assignment; the actual SSA operand is a pointer. +func llvmNewprocSignature(v *Value, aux *AuxCall, sig llvmFuncSignature) llvmFuncSignature { + if aux == nil || aux.Fn != ir.Syms.Newproc { + return sig + } + if aux.ABI().Which() != obj.ABIInternal { + v.Fatalf("runtime.newproc uses unsupported ABI %v", aux.ABI().Which()) + } + if aux.NArgs() != 1 || aux.NResults() != 0 || !aux.TypeOfArg(0).IsUintptr() { + v.Fatalf("runtime.newproc has unexpected raw call signature") + } + if len(v.Args) != 2 || v.Args[0].Type == nil || !v.Args[0].Type.IsPtrShaped() { + v.Fatalf("runtime.newproc argument is not pointer-shaped") + } + params := append([]llvm.Type(nil), sig.Type.ParamTypes()...) + params[0] = GlobalCtxt.PointerType(0) + sig.Type = llvm.FunctionType(sig.ReturnType, params, false) + return sig +} + func (lfc *LLVMFuncContext) staticCall(v *Value) llvm.Value { aux := auxToCall(v.Aux) if aux == nil || aux.Fn == nil { @@ -559,7 +581,7 @@ func (lfc *LLVMFuncContext) staticCall(v *Value) llvm.Value { v.Fatalf("static call to %s has %d LLVM arguments, want %d", aux.Fn.Name, got, want) } - sig := llvmSignature(aux) + sig := llvmNewprocSignature(v, aux, llvmSignature(aux)) cc := llvmCallConv(aux.ABI().Which()) fn := getOrInsertLLVMFunction(aux.Fn.Name, sig, cc) args := make([]llvm.Value, 0, aux.NArgs()) diff --git a/test/codegen/llvm_newproc.go b/test/codegen/llvm_newproc.go new file mode 100644 index 00000000000000..ecce80bff6176f --- /dev/null +++ b/test/codegen/llvm_newproc.go @@ -0,0 +1,18 @@ +// asmcheck + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package codegen + +// LLVM-LABEL: define goabiinternal void @codegen.llvmNewproc( +// LLVM: call goabiinternal void @runtime.newproc(ptr +// LLVM-NOT: call goabiinternal void @runtime.newproc(i64 +// LLVM: ret void +// LLVM: declare goabiinternal void @runtime.newproc(ptr) +var llvmNewprocSink int + +func llvmNewproc(value int) { + go func() { llvmNewprocSink = value }() +} diff --git a/test/llvm_newproc_gc.go b/test/llvm_newproc_gc.go new file mode 100644 index 00000000000000..e782543878d2a6 --- /dev/null +++ b/test/llvm_newproc_gc.go @@ -0,0 +1,49 @@ +// run + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "runtime" + +type newprocPayload struct { + value int + link *newprocPayload +} + +// launch grows the caller stack before materializing an escaping funcval and +// passing it to runtime.newproc. The funcval is the only object passed to the +// runtime call; its captured payload is checked after another GC. +// +//go:noinline +func launch(payload *newprocPayload, depth int, done chan<- int) { + var padding [64]uintptr + padding[0] = uintptr(depth) + if depth != 0 { + launch(payload, depth-1, done) + runtime.KeepAlive(padding) + return + } + go func() { + runtime.GC() + done <- payload.value + payload.link.value + }() +} + +func main() { + const rounds = 8 + done := make(chan int, rounds) + for i := 0; i < rounds; i++ { + payload := &newprocPayload{value: 100 + i, link: &newprocPayload{value: i}} + launch(payload, 256, done) + payload = nil + runtime.GC() + } + for i := 0; i < rounds; i++ { + if got := <-done; got < 100 || got >= 100+2*rounds || got%2 != 0 { + panic("runtime.newproc lost its pointer-typed funcval across GC or stack growth") + } + } +} diff --git a/test/llvm_tests.json b/test/llvm_tests.json index 741aa9a95e191b..b3c8a8bff48130 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -34,6 +34,7 @@ "codegen/llvm_data_sections.go": "preserve data, bss, noptrdata, and noptrbss identities in LLVM IR", "codegen/llvm_dereference.go": "address-taken named stack results loaded for return", "codegen/llvm_memops.go": "pointer-free zero and overlap-safe move intrinsics, runtime memequal, and native-int slice masks", + "codegen/llvm_newproc.go": "pointer-typed funcval argument for the runtime.newproc raw ABI call", "codegen/memops.go": "typed indexed loads, stores, comparisons, bounds checks, and scalar memory operations", "codegen/memops_bigoffset.go": "typed loads and stores at offsets beyond signed 32-bit byte displacement", "codegen/multiply.go": "integer multiplication across positive, negative, and zero constants", @@ -91,6 +92,7 @@ "llvm_aggregate_call_argument.go": "pointer-containing struct used only as a call argument, without caller gc-live or relocation", "llvm_memeq.go": "raw memory equality for true, false, different-length, and offset substring inputs", "llvm_move.go": "overlap-safe, aligned, and runtime-helper memory moves", + "llvm_newproc_gc.go": "pointer-typed runtime.newproc funcval across stack growth and repeated GC", "llvm_private_string.go": "frontend private string constant materialization, GoObj linking, and execution", "llvm_slicemask.go": "slice start, middle, and zero-capacity end boundary semantics", "llvm_zero.go": "small and aligned pointer-free zeroing",