func (m *MemLog) Update(addr uint64, size uint32, p []byte, write bool) {
	var delta *memDelta
	before := false
	if delta = m.Adjacent(addr, size, write); delta != nil {
		if addr < delta.addr {
			delta.addr -= uint64(size)
			before = true
		}
		var tag byte
		if before {
			tag = '<'
		} else {
			tag = '>'
		}
		if delta.tag == ' ' {
			delta.tag = tag
		} else if delta.tag != tag {
			delta.tag = '~'
		}
	} else {
		delta = &memDelta{addr, nil, nil, 0, write, ' '}
		m.log = append(m.log, delta)
	}
	if write {
		if before {
			data := make([]byte, len(p), len(p)+len(delta.data))
			copy(data, p)
			copy(data[len(p):], delta.data)
			delta.data = data
			delta.last = delta.data[:len(p)]
		} else {
			delta.data = append(delta.data, p...)
			delta.last = delta.data[len(delta.data)-len(p):]
		}
	}
	delta.size += size
}
Update inserts a new read or write event into the log
package trace
import (
	"fmt"
	"strings"
	"github.com/lunixbochs/usercorn/go/models"
	"github.com/lunixbochs/usercorn/go/models/cpu"
)
type OpMemBatch struct {
	OpNop
	Ops []models.Op
}
func (o *OpMemBatch) Render(mem *cpu.Mem) string {
	var out []string
	for _, op := range o.Ops {
		var addr uint64
		var data []byte
		var t string
		switch v := op.(type) {
		case *OpMemWrite:
			addr, data, t = v.Addr, v.Data, "W"
		case *OpMemRead:
			addr = v.Addr
			data, _ = mem.MemRead(v.Addr, uint64(v.Size))
			t = "R"
		}
		for i, line := range models.HexDump(addr, data, 32) {
			if i == 0 {
				out = append(out, fmt.Sprintf("%s %s %s\n", t, line, t))
			} else {
				out = append(out, fmt.Sprintf("   %s\n", line))
			}
		}
	}
	return strings.Join(out, "")
}
type MemBatch struct {
	memOps  []models.Op
	pc      uint64
	selfjmp bool
}
func (m *MemBatch) Filter(op models.Op) []models.Op {
	switch v := op.(type) {
	case *OpJmp:
		m.selfjmp = v.Addr == m.pc
		m.pc = v.Addr
		if !m.selfjmp {
			return append(m.Flush(), op)
		}
	case *OpStep:
		m.pc += uint64(v.Size)
		if m.selfjmp {
			m.selfjmp = false
			return append(m.Flush(), op)
		}
	case *OpMemRead, *OpMemWrite:
		m.memOps = append(m.memOps, op)
	}
	return []models.Op{op}
}
func (m *MemBatch) Flush() []models.Op {
	log := &MemLog{}
	for _, op := range m.memOps {
		switch v := op.(type) {
		case *OpMemWrite:
			log.Update(v.Addr, uint32(len(v.Data)), v.Data, true)
		case *OpMemRead:
			log.Update(v.Addr, v.Size, nil, false)
		}
	}
	m.memOps = nil
	ops := make([]models.Op, 0, len(log.log))
	for _, d := range log.log {
		if d.write {
			ops = append(ops, &OpMemWrite{d.addr, d.data})
		} else {
			ops = append(ops, &OpMemRead{d.addr, d.size})
		}
	}
	if len(ops) > 0 {
		return []models.Op{&OpMemBatch{Ops: ops}}
	}
	return nil
}
type memDelta struct {
	addr  uint64
	last  []byte
	data  []byte
	size  uint32
	write bool
	tag   byte
}
type MemLog struct {
	log []*memDelta
}
func (m *MemLog) Adjacent(addr uint64, size uint32, write bool) *memDelta {
	for _, delta := range m.log {
		if delta.write != write {
			continue
		}
		if addr == delta.addr+uint64(delta.size) || addr == delta.addr-uint64(size) {
			return delta
		}
	}
	return nil
}