Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/cmd/compile/internal/ssa/ssa2llvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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())
Expand Down
18 changes: 18 additions & 0 deletions test/codegen/llvm_newproc.go
Original file line number Diff line number Diff line change
@@ -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 }()
}
49 changes: 49 additions & 0 deletions test/llvm_newproc_gc.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
}
2 changes: 2 additions & 0 deletions test/llvm_tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down