From 565b141c38da7c9c86499c67bf62ac7dd69a5aae Mon Sep 17 00:00:00 2001 From: Mel Date: Sun, 12 Jun 2022 14:48:52 +0000 Subject: Add simple Lang BC Decompiler --- pkg/lang/vm/text/decompiler.go | 137 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 pkg/lang/vm/text/decompiler.go (limited to 'pkg/lang/vm/text/decompiler.go') diff --git a/pkg/lang/vm/text/decompiler.go b/pkg/lang/vm/text/decompiler.go new file mode 100644 index 0000000..c8922ec --- /dev/null +++ b/pkg/lang/vm/text/decompiler.go @@ -0,0 +1,137 @@ +package text + +import ( + "encoding/binary" + "fmt" + "jinx/pkg/lang/vm/code" + "jinx/pkg/libs/rangemap" + "math" + "strconv" + "strings" +) + +type Decompiler struct { + c code.Code + + pcToLine rangemap.RangeMap[int] +} + +func NewDecompiler(c code.Code) *Decompiler { + return &Decompiler{ + c: c, + pcToLine: rangemap.New[int](), + } +} + +func (d *Decompiler) Decompile() string { + lines := make([]string, 0) + bc := d.c.Code() + + for len(bc) != 0 { + line, rest := d.decompileInstruction(bc) + bc = rest + + d.pcToLine.AppendToLast(d.c.Len()-len(bc), len(lines)) + + lines = append(lines, line) + } + + return strings.Join(lines, "\n") +} + +func (d *Decompiler) decompileInstruction(bc code.Raw) (string, code.Raw) { + op := code.Op(bc[0]) + opString := OpToString(op) + if opString == "unknown" { + return fmt.Sprintf("unknown(%x)", bc[0]), bc[1:] + } + + switch op { + // Operations that take no arguments. + case code.OpNop, + code.OpHalt, + code.OpPushTrue, + code.OpPushFalse, + code.OpPushNull, + code.OpPushArray, + code.OpPushObject, + code.OpDrop, + code.OpAnchorType, + code.OpAdd, + code.OpSub, + code.OpMod, + code.OpIndex, + code.OpLte, + code.OpRet, + code.OpTempArrLen, + code.OpTempArrPush: + return opString, bc[1:] + + // Operations that take an int. + case code.OpPushInt, + code.OpGetLocal, + code.OpSetLocal, + code.OpGetEnv, + code.OpSetEnv, + code.OpAddToEnv, + code.OpCall: + i, rest := d.decompileInt(bc[1:]) + return fmt.Sprintf("%s %s", opString, i), rest + + // Operations that take a float. + case code.OpPushFloat: + f, rest := d.decompileFloat(bc[1:]) + return fmt.Sprintf("%s %s", opString, f), rest + + // Operations that take a string. + case code.OpPushString, + code.OpPushType, + code.OpGetGlobal, + code.OpSetMember, + code.OpGetMember: + s, rest := d.decompileString(bc[1:]) + return fmt.Sprintf("%s %s", opString, s), rest + + // Operations that take a pc, belonging to a function. + case code.OpPushFunction: + // TODO: Add function labels to output and give them names. + fallthrough + + // Operations that take a pc, belonging to a non-call jump. (when branching) + case code.OpJmp, + code.OpJt, + code.OpJf: + // TODO: Add jump labels to output and give them names. + i, rest := d.decompileInt(bc[1:]) + return fmt.Sprintf("%s @%s", opString, i), rest + } + + panic("decompiler can't decompile op: " + opString) +} + +func (d *Decompiler) decompileInt(bc code.Raw) (string, code.Raw) { + i := binary.LittleEndian.Uint64(bc[:8]) + return strconv.FormatInt(int64(i), 10), bc[8:] +} + +func (d *Decompiler) decompileFloat(bc code.Raw) (string, code.Raw) { + i := binary.LittleEndian.Uint64(bc[:8]) + f := math.Float64frombits(i) + return strconv.FormatFloat(f, 'f', -1, 64), bc[8:] +} + +func (d *Decompiler) decompileString(bc code.Raw) (string, code.Raw) { + var buf strings.Builder + buf.WriteString("\"") + end := 0 + for i, b := range bc { + if b == 0 { + end = i + break + } + buf.WriteByte(b) + } + buf.WriteString("\"") + + return buf.String(), bc[end+1:] +} -- cgit 1.4.1