Skip to content

Commit

Permalink
feat: added the option to output opcodes as dot (#440)
Browse files Browse the repository at this point in the history
  • Loading branch information
orisano committed Mar 19, 2023
1 parent 098041a commit 4d199a4
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 0 deletions.
83 changes: 83 additions & 0 deletions internal/encoder/opcode.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package encoder

import (
"bytes"
"fmt"
"sort"
"strings"
"unsafe"

Expand Down Expand Up @@ -555,6 +557,87 @@ func (c *Opcode) Dump() string {
return strings.Join(codes, "\n")
}

func (c *Opcode) DumpDOT() string {
type edge struct {
from, to *Opcode
label string
weight int
}
var edges []edge

b := &bytes.Buffer{}
fmt.Fprintf(b, "digraph \"%p\" {\n", c.Type)
fmt.Fprintln(b, "mclimit=1.5;\nrankdir=TD;\nordering=out;\nnode[shape=box];")
for code := c; !code.IsEnd(); {
label := code.Op.String()
fmt.Fprintf(b, "\"%p\" [label=%q];\n", code, label)
if p := code.Next; p != nil {
edges = append(edges, edge{
from: code,
to: p,
label: "Next",
weight: 10,
})
}
if p := code.NextField; p != nil {
edges = append(edges, edge{
from: code,
to: p,
label: "NextField",
weight: 2,
})
}
if p := code.End; p != nil {
edges = append(edges, edge{
from: code,
to: p,
label: "End",
weight: 1,
})
}
if p := code.Jmp; p != nil {
edges = append(edges, edge{
from: code,
to: p.Code,
label: "Jmp",
weight: 1,
})
}

switch code.Op.CodeType() {
case CodeSliceHead:
code = code.Next
case CodeMapHead:
code = code.Next
case CodeArrayElem, CodeSliceElem:
code = code.End
case CodeMapKey:
code = code.End
case CodeMapValue:
code = code.Next
case CodeMapEnd:
code = code.Next
case CodeStructField:
code = code.Next
case CodeStructEnd:
code = code.Next
default:
code = code.Next
}
if code.IsEnd() {
fmt.Fprintf(b, "\"%p\" [label=%q];\n", code, code.Op.String())
}
}
sort.Slice(edges, func(i, j int) bool {
return edges[i].to.DisplayIdx < edges[j].to.DisplayIdx
})
for _, e := range edges {
fmt.Fprintf(b, "\"%p\" -> \"%p\" [label=%q][weight=%d];\n", e.from, e.to, e.label, e.weight)
}
fmt.Fprint(b, "}")
return b.String()
}

func newSliceHeaderCode(ctx *compileContext, typ *runtime.Type) *Opcode {
idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
Expand Down
1 change: 1 addition & 0 deletions internal/encoder/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Option struct {
ColorScheme *ColorScheme
Context context.Context
DebugOut io.Writer
DebugDOTOut io.WriteCloser
}

type EncodeFormat struct {
Expand Down
6 changes: 6 additions & 0 deletions internal/encoder/vm/debug_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package vm

import (
"fmt"
"io"

"github.com/goccy/go-json/internal/encoder"
)
Expand All @@ -14,6 +15,11 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
} else {
code = codeSet.NoescapeKeyCode
}
if wc := ctx.Option.DebugDOTOut; wc != nil {
_, _ = io.WriteString(wc, code.DumpDOT())
wc.Close()
ctx.Option.DebugDOTOut = nil
}

if err := recover(); err != nil {
w := ctx.Option.DebugOut
Expand Down
7 changes: 7 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ func DebugWith(w io.Writer) EncodeOptionFunc {
}
}

// DebugDOT sets the destination to write opcodes graph.
func DebugDOT(w io.WriteCloser) EncodeOptionFunc {
return func(opt *EncodeOption) {
opt.DebugDOTOut = w
}
}

// Colorize add an identifier for coloring to the string of the encoded result.
func Colorize(scheme *ColorScheme) EncodeOptionFunc {
return func(opt *EncodeOption) {
Expand Down

0 comments on commit 4d199a4

Please sign in to comment.