Skip to content

Commit

Permalink
proc: use stack machine to evaluate expressions
Browse files Browse the repository at this point in the history
This commit splits expression evaluation into two parts. The first part (in
pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of
instructions (defined in pkg/proc/evalop/ops.go) for a stack machine
(defined by `proc.(*evalStack)`).
The second part is a stack machine (implemented by `proc.(*EvalScope).eval`
and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the
main mode it executes inteructions from the list (by calling `evalOne`), in
the second mode it executes the call injection protocol by calling
`funcCallStep` repeatedly until it either the protocol finishes, needs more
input from the stack machine (to set call arguments) or fails.

This approach has several benefits:

- it is now possible to remove the goroutine we use to evaluate expression
  and the channel used to communicate with the Continue loop.
- every time we resume the target to execute the call injection protocol we
  need to update several local variables to match the changed state of the
  target, this is now done at the top level of the evaluation loop instead of
  being hidden inside a recurisive evaluator
- using runtime.Pin to pin addresses returned by an injected call would
  allow us to use a more natural evaluation order for function calls, which
  would solve some bugs go-delve#3310, allow users to inspect values returned by a
  call injection go-delve#1599 and allow implementing some other features go-delve#1465. Doing
  this with the recursive evaluator, while keeping backwards compatibility
  with versions of Go that do not have runtime.Pin is very hard. However after
  this change we can simply conditionally change how compileFunctionCall works
  and add some opcodes.
  • Loading branch information
aarzilli committed Sep 22, 2023
1 parent ebc3e61 commit 473f003
Show file tree
Hide file tree
Showing 11 changed files with 1,882 additions and 795 deletions.
71 changes: 71 additions & 0 deletions pkg/dwarf/godwarf/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package godwarf

import (
"fmt"
"reflect"
)

// FakeSliceType synthesizes a slice type with the given field type.
func FakeSliceType(fieldType Type) Type {
return &SliceType{
StructType: StructType{
CommonType: CommonType{
ByteSize: 24,
Name: "",
},
StructName: "[]" + fieldType.Common().Name,
Kind: "struct",
Field: nil,
},
ElemType: fieldType,
}
}

// FakeBasicType synthesizes a basic type numeric type (int8, uint16,
// float32, etc)
func FakeBasicType(name string, bitSize int) Type {
byteSize := bitSize / 8
szr := popcnt(uint64(byteSize^(byteSize-1))) - 1 // position of rightmost 1 bit, minus 1

basic := func(kind reflect.Kind) BasicType {
return BasicType{
CommonType: CommonType{
ByteSize: int64(byteSize),
Name: fmt.Sprintf("%s%d", name, bitSize),
ReflectKind: kind,
},
BitSize: int64(bitSize),
BitOffset: 0,
}
}

switch name {
case "int":
return &IntType{BasicType: basic(reflect.Int8 + reflect.Kind(szr))}
case "uint":
return &UintType{BasicType: basic(reflect.Uint8 + reflect.Kind(szr))}
case "float":
return &FloatType{BasicType: basic(reflect.Float32 + reflect.Kind(szr-2))}
case "complex":
return &ComplexType{BasicType: basic(reflect.Complex64 + reflect.Kind(szr-3))}
default:
panic("unsupported")
}
}

// popcnt is the number of bits set to 1 in x.
// It's the same as math/bits.OnesCount64, copied here so that we can build
// on versions of go that don't have math/bits.
func popcnt(x uint64) int {
const m0 = 0x5555555555555555 // 01010101 ...
const m1 = 0x3333333333333333 // 00110011 ...
const m2 = 0x0f0f0f0f0f0f0f0f // 00001111 ...
const m = 1<<64 - 1
x = x>>1&(m0&m) + x&(m0&m)
x = x>>2&(m1&m) + x&(m1&m)
x = (x>>4 + x) & (m2 & m)
x += x >> 8
x += x >> 16
x += x >> 32
return int(x) & (1<<7 - 1)
}
Loading

0 comments on commit 473f003

Please sign in to comment.