Skip to content

Commit 6670dae

Browse files
authored
Merge pull request #2143 from cpunion/codex/dwarf-opt-pipeline
debug: keep optimization and SSA rewrites DWARF-safe
2 parents 9f0c76a + 2381bb9 commit 6670dae

18 files changed

Lines changed: 526 additions & 86 deletions

cl/rewrite_internal_test.go

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,22 @@ func compileWithRewrites(t *testing.T, src string, rewrites map[string]string) s
3131
}
3232

3333
func compileWithRewritesTarget(t *testing.T, src string, rewrites map[string]string, target *llssa.Target) string {
34+
return compileWithRewritesModeTarget(t, src, rewrites,
35+
ssa.SanityCheckFunctions|ssa.InstantiateGenerics, target)
36+
}
37+
38+
func compileWithRewritesMode(t *testing.T, src string, rewrites map[string]string, mode ssa.BuilderMode) string {
39+
return compileWithRewritesModeTarget(t, src, rewrites, mode, nil)
40+
}
41+
42+
func compileWithRewritesModeTarget(t *testing.T, src string, rewrites map[string]string, mode ssa.BuilderMode, target *llssa.Target) string {
3443
t.Helper()
3544
fset := token.NewFileSet()
3645
file, err := parser.ParseFile(fset, "rewrite.go", src, parser.ParseComments)
3746
if err != nil {
3847
t.Fatalf("parse failed: %v", err)
3948
}
4049
importer := gpackages.NewImporter(fset)
41-
mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics
4250
pkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer}, fset,
4351
types.NewPackage(file.Name.Name, file.Name.Name), []*ast.File{file}, mode)
4452
if err != nil {
@@ -234,6 +242,75 @@ func Use() callbackType { return CallbackTypes[1] }
234242
}
235243
}
236244

245+
func TestStaticGlobalSliceLiteralInitWithDebugRefs(t *testing.T) {
246+
const src = `package staticinit
247+
248+
var CallbackTypes = []string{"BeforeCreate", "AfterCreate"}
249+
250+
func Use() string { return CallbackTypes[1] }
251+
`
252+
ir := compileWithRewritesMode(t, src, nil,
253+
ssa.SanityCheckFunctions|ssa.InstantiateGenerics|ssa.GlobalDebug)
254+
for _, want := range []string{
255+
`@"staticinit.CallbackTypes$data" = global [2 x %"github.com/goplus/llgo/runtime/internal/runtime.String"]`,
256+
`@staticinit.CallbackTypes = global %"github.com/goplus/llgo/runtime/internal/runtime.Slice" { ptr @"staticinit.CallbackTypes$data", i64 2, i64 2 }`,
257+
`c"BeforeCreate"`,
258+
`c"AfterCreate"`,
259+
} {
260+
if !strings.Contains(ir, want) {
261+
t.Fatalf("missing static slice initializer %q with debug refs:\n%s", want, ir)
262+
}
263+
}
264+
assertNoStoreToGlobal(t, ir, "@staticinit.CallbackTypes")
265+
if strings.Contains(ir, "runtime.AllocZ") {
266+
t.Fatalf("static slice initializer allocates at runtime with debug refs:\n%s", ir)
267+
}
268+
}
269+
270+
func TestStaticSliceInitRejectsExecutableReferrers(t *testing.T) {
271+
const src = `package foo
272+
273+
var Values []int
274+
275+
func useSlice([]int) {}
276+
func usePointer(*int) {}
277+
278+
func sliceUser() {
279+
backing := [2]int{1, 2}
280+
values := backing[:]
281+
Values = values
282+
useSlice(values)
283+
}
284+
285+
func elementUser() {
286+
var backing [2]int
287+
elem := &backing[0]
288+
*elem = 1
289+
usePointer(elem)
290+
Values = backing[:]
291+
}
292+
`
293+
ssapkg := buildSSAPackage(t, src)
294+
global := ssapkg.Members["Values"].(*ssa.Global)
295+
for _, name := range []string{"sliceUser", "elementUser"} {
296+
fn := ssapkg.Func(name)
297+
var globalStore *ssa.Store
298+
for _, block := range fn.Blocks {
299+
for _, instr := range block.Instrs {
300+
if store, ok := instr.(*ssa.Store); ok && store.Addr == global {
301+
globalStore = store
302+
}
303+
}
304+
}
305+
if globalStore == nil {
306+
t.Fatalf("%s: store to Values not found", name)
307+
}
308+
if _, ok := staticSliceInitOf(globalStore); ok {
309+
t.Fatalf("%s: static slice init accepted an executable referrer", name)
310+
}
311+
}
312+
}
313+
237314
func TestStaticGlobalZeroSizedSliceLiteralFallsBack(t *testing.T) {
238315
const src = `package staticinit
239316

cl/static_init.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,16 +209,16 @@ func staticSliceInitOf(store *ssa.Store) (*staticSliceInit, bool) {
209209
values: make(map[int]*ssa.Const),
210210
instrs: []ssa.Instruction{alloc, slice, store},
211211
}
212-
sliceRefs := slice.Referrers()
213-
if sliceRefs == nil || len(*sliceRefs) != 1 || (*sliceRefs)[0] != store {
212+
sliceRefs, ok := nonDebugReferrers(slice)
213+
if !ok || len(sliceRefs) != 1 || sliceRefs[0] != store {
214214
return nil, false
215215
}
216-
refs := alloc.Referrers()
217-
if refs == nil {
216+
refs, ok := nonDebugReferrers(alloc)
217+
if !ok {
218218
return nil, false
219219
}
220220
seenSlice := false
221-
for _, ref := range *refs {
221+
for _, ref := range refs {
222222
switch ref := ref.(type) {
223223
case *ssa.Slice:
224224
if ref != slice || seenSlice {
@@ -233,11 +233,11 @@ func staticSliceInitOf(store *ssa.Store) (*staticSliceInit, bool) {
233233
if !ok || index >= int(array.Len()) {
234234
return nil, false
235235
}
236-
indexRefs := ref.Referrers()
237-
if indexRefs == nil || len(*indexRefs) != 1 {
236+
indexRefs, ok := nonDebugReferrers(ref)
237+
if !ok || len(indexRefs) != 1 {
238238
return nil, false
239239
}
240-
elemStore, ok := (*indexRefs)[0].(*ssa.Store)
240+
elemStore, ok := indexRefs[0].(*ssa.Store)
241241
if !ok || elemStore.Addr != ref {
242242
return nil, false
243243
}

internal/build/build.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -591,17 +591,15 @@ func Build(inv Invocation) ([]Package, error) {
591591

592592
buildMode := ssaBuildMode
593593
cabiOptimize := true
594-
passOpt := true
595-
if emitDebugInfo || mode == ModeGen {
596-
passOpt = false
597-
}
594+
passOpt := shouldRunLLVMPasses(mode)
598595
if emitDebugInfo {
599596
buildMode |= ssa.GlobalDebug
600597
cabiOptimize = false
601598
}
602599
if !IsOptimizeEnabled() {
603600
buildMode |= ssa.NaiveForm
604601
}
602+
prog.SetDebugInfoOptimized(passOpt && conf.OptLevel != optlevel.O0)
605603
progSSA := ssa.NewProgram(initial[0].Fset, buildMode)
606604
patches := make(cl.Patches, len(altPkgPaths))
607605
altEntries := registerAltSSAPkgs(progSSA, patches, altPkgs[1:], conf, verbose)
@@ -2618,6 +2616,10 @@ func llvmPassPipeline(level optlevel.Level, ltoMode lto.Mode) string {
26182616
}
26192617
}
26202618

2619+
func shouldRunLLVMPasses(mode Mode) bool {
2620+
return mode != ModeGen
2621+
}
2622+
26212623
func IsWasiThreadsEnabled() bool {
26222624
return isEnvOn(llgoWasiThreads, true)
26232625
}

internal/build/optlevel_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,14 @@ func TestLLVMPassPipeline(t *testing.T) {
7171
}
7272
}
7373
}
74+
75+
func TestShouldRunLLVMPasses(t *testing.T) {
76+
for _, mode := range []Mode{ModeBuild, ModeInstall, ModeRun, ModeTest, ModeCmpTest} {
77+
if !shouldRunLLVMPasses(mode) {
78+
t.Errorf("shouldRunLLVMPasses(%v) = false, want true", mode)
79+
}
80+
}
81+
if shouldRunLLVMPasses(ModeGen) {
82+
t.Fatal("shouldRunLLVMPasses(ModeGen) = true, want false")
83+
}
84+
}

internal/build/ssa_order_fix.go

Lines changed: 82 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import (
2121
// the first return value as a load before the call, which makes o appear unchanged
2222
// to the return value in our backend.
2323
//
24-
// This pass moves loads of local allocs used only for the final Return results
25-
// to after any intervening calls that use the same alloc pointer, matching the
26-
// behavior of the Go compiler for the stdlib cases we rely on (e.g. crypto/x509.ParseOID).
24+
// This pass moves loads of local allocs that feed a Return result and have no
25+
// intervening executable use before that Return to after any intervening calls
26+
// that use the same alloc pointer, matching the behavior of the Go compiler for
27+
// the stdlib cases we rely on (e.g. crypto/x509.ParseOID).
2728
func fixSSAOrder(pkg *ssa.Package, files []*ast.File) {
2829
if pkg == nil {
2930
return
@@ -186,7 +187,11 @@ func moveAssignDepsAfterRecv(b *ssa.BasicBlock, roots []ssa.Value, recv ssa.Valu
186187
if len(move) == 0 {
187188
return false
188189
}
189-
if moveWouldBreakSSA(b.Instrs, move, recvIdx) {
190+
// Metadata uses must follow the definitions they describe rather than
191+
// blocking an otherwise safe source-order repair.
192+
moved := movedValuesForIndices(b.Instrs, move)
193+
includeDebugRefsForMovedValues(b.Instrs, move, moved, 0, recvIdx)
194+
if moveWouldBreakSSA(b.Instrs, move, recvIdx, moved) {
190195
return false
191196
}
192197
deps := make([]ssa.Instruction, 0, len(move))
@@ -205,13 +210,47 @@ func moveAssignDepsAfterRecv(b *ssa.BasicBlock, roots []ssa.Value, recv ssa.Valu
205210
return true
206211
}
207212

208-
func moveWouldBreakSSA(instrs []ssa.Instruction, move map[int]struct{}, recvIdx int) bool {
213+
func movedValuesForIndices(instrs []ssa.Instruction, move map[int]struct{}) map[ssa.Value]struct{} {
209214
moved := make(map[ssa.Value]struct{}, len(move))
210215
for i := range move {
216+
if i < 0 || i >= len(instrs) {
217+
continue
218+
}
211219
if v, ok := instrs[i].(ssa.Value); ok && v != nil {
212220
moved[v] = struct{}{}
213221
}
214222
}
223+
return moved
224+
}
225+
226+
// includeDebugRefsForMovedValues adds metadata-only uses of moved values to
227+
// move. The moved value set is supplied by the caller so the same set can be
228+
// reused by the subsequent SSA safety check without rescanning move.
229+
func includeDebugRefsForMovedValues(instrs []ssa.Instruction, move map[int]struct{}, moved map[ssa.Value]struct{}, from, through int) {
230+
if from < 0 {
231+
from = 0
232+
}
233+
if through > len(instrs) {
234+
through = len(instrs)
235+
}
236+
for i := from; i < through; i++ {
237+
if _, moving := move[i]; moving {
238+
continue
239+
}
240+
ref, ok := instrs[i].(*ssa.DebugRef)
241+
if !ok {
242+
continue
243+
}
244+
for v := range moved {
245+
if instrUsesValue(ref, v) {
246+
move[i] = struct{}{}
247+
break
248+
}
249+
}
250+
}
251+
}
252+
253+
func moveWouldBreakSSA(instrs []ssa.Instruction, move map[int]struct{}, recvIdx int, moved map[ssa.Value]struct{}) bool {
215254
for i := 0; i <= recvIdx && i < len(instrs); i++ {
216255
if _, moving := move[i]; moving {
217256
continue
@@ -302,11 +341,20 @@ func fixSSAOrderBlock(b *ssa.BasicBlock) {
302341
continue
303342
}
304343

305-
// If the loaded value is used by any instruction between its current
306-
// position and the return (excluding return itself), moving it may place
307-
// its definition after one of those uses and break SSA form.
344+
// DebugRefs are metadata-only and move with the value they describe. Any
345+
// executable use before Return still makes reordering unsafe.
346+
movingIndices := map[int]struct{}{loadIdx: {}}
347+
moved := movedValuesForIndices(b.Instrs, movingIndices)
348+
includeDebugRefsForMovedValues(b.Instrs, movingIndices, moved, loadIdx+1, retIdx)
349+
moving := make(map[ssa.Instruction]struct{}, len(movingIndices))
350+
for i := range movingIndices {
351+
moving[b.Instrs[i]] = struct{}{}
352+
}
308353
usedBeforeReturn := false
309354
for i := loadIdx + 1; i < retIdx; i++ {
355+
if _, moving := movingIndices[i]; moving {
356+
continue
357+
}
310358
if instrUsesValue(b.Instrs[i], u) {
311359
usedBeforeReturn = true
312360
break
@@ -316,9 +364,7 @@ func fixSSAOrderBlock(b *ssa.BasicBlock) {
316364
continue
317365
}
318366

319-
// Move the load right after the last call (but before Return).
320-
b.Instrs = moveInstr(b.Instrs, loadIdx, lastCallIdx+1)
321-
// Adjust retIdx for subsequent moves in this block.
367+
b.Instrs = moveInstrsAfter(b.Instrs, moving, b.Instrs[lastCallIdx])
322368
retIdx = indexOfInstr(b.Instrs, ret)
323369
}
324370
}
@@ -391,41 +437,34 @@ func valueDependsOn(v, target ssa.Value, seen map[ssa.Value]struct{}) bool {
391437
return false
392438
}
393439

394-
// moveInstr moves instrs[from] to position to (like inserting before to),
395-
// preserving relative order of other elements.
396-
func moveInstr(instrs []ssa.Instruction, from, to int) []ssa.Instruction {
397-
if from < 0 || from >= len(instrs) {
398-
return instrs
399-
}
400-
if to < 0 {
401-
to = 0
402-
}
403-
if to > len(instrs) {
404-
to = len(instrs)
405-
}
406-
if from == to || from+1 == to {
440+
// moveInstrsAfter moves selected instructions as a stable group immediately
441+
// after anchor. The anchor must not be in moving; callers use an instruction
442+
// that remains in the block. It returns instrs unchanged when moving is empty,
443+
// or anchor is nil or absent.
444+
func moveInstrsAfter(instrs []ssa.Instruction, moving map[ssa.Instruction]struct{}, anchor ssa.Instruction) []ssa.Instruction {
445+
if len(moving) == 0 || anchor == nil {
407446
return instrs
408447
}
409-
410-
ins := instrs[from]
411-
// Remove.
412-
copy(instrs[from:], instrs[from+1:])
413-
instrs = instrs[:len(instrs)-1]
414-
415-
// Recompute insertion index after removal.
416-
if to > from {
417-
to--
418-
}
419-
if to < 0 {
420-
to = 0
448+
if _, ok := moving[anchor]; ok {
449+
panic("moveInstrsAfter: anchor is in moving set")
421450
}
422-
if to > len(instrs) {
423-
to = len(instrs)
451+
moved := make([]ssa.Instruction, 0, len(moving))
452+
remaining := make([]ssa.Instruction, 0, len(instrs))
453+
for _, instr := range instrs {
454+
if _, ok := moving[instr]; ok {
455+
moved = append(moved, instr)
456+
continue
457+
}
458+
remaining = append(remaining, instr)
459+
}
460+
for i, instr := range remaining {
461+
if instr == anchor {
462+
ret := make([]ssa.Instruction, 0, len(instrs))
463+
ret = append(ret, remaining[:i+1]...)
464+
ret = append(ret, moved...)
465+
ret = append(ret, remaining[i+1:]...)
466+
return ret
467+
}
424468
}
425-
426-
// Insert.
427-
instrs = append(instrs, nil)
428-
copy(instrs[to+1:], instrs[to:])
429-
instrs[to] = ins
430469
return instrs
431470
}

0 commit comments

Comments
 (0)