initial commit

This commit is contained in:
ipc 2023-05-28 17:31:59 +02:00
commit 380acbdc5f
10 changed files with 894 additions and 0 deletions

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2022, 2023 ipc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

10
Makefile Normal file
View File

@ -0,0 +1,10 @@
all: hypo hypoc
hypo: $(wildcard cmd/hypo/*.go) $(wildcard cpu/*.go)
go build ./cmd/hypo
hypoc: $(wildcard cmd/hypoc/*.go) $(wildcard asm/*.go)
go build ./cmd/hypoc
clean:
rm -f hypo hypoc

18
README.md Normal file
View File

@ -0,0 +1,18 @@
# hypo
hypo is an interpreter for the hypo architecture.
# hypoc
hypoc is the hypo assembler. Samples of assembler code are
included in the sample directory.
# Install
To compile, type in:
`make`
To install the binaries:
`make install`

368
asm/asm.go Normal file
View File

@ -0,0 +1,368 @@
package asm
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"strconv"
"strings"
"unicode"
)
const (
Id = iota
Label
Reg
Addr
Eof
)
const ErrThreshold = 8
type Symbol struct {
Type int
Val string
Line int
}
type Instruction struct {
Op byte
Params []int
}
type Reader struct {
sym []Symbol
nsym int
}
type Writer struct {
buf bytes.Buffer
pc uint32
lab map[string]uint32
addr map[uint32]string
f io.Writer
}
var Hdr = []byte{0x48, 0x59, 0x50, 0x00}
var syms = map[byte]int{
'%': Reg,
'$': Addr,
}
var inst = map[string]Instruction{
"nop": {OpNop, []int{}},
"ld": {OpLd, []int{Reg, Reg}},
"lr": {OpLr, []int{Addr, Reg}},
"st": {OpSt, []int{Reg, Reg}},
"add": {OpAdd, []int{Reg, Reg, Reg}},
"sub": {OpSub, []int{Reg, Reg, Reg}},
"addi": {OpAddi, []int{Reg, Addr, Reg}},
"subi": {OpSubi, []int{Reg, Addr, Reg}},
"p": {OpP, []int{Reg}},
"beq": {OpBeq, []int{Reg, Reg, Addr}},
"bne": {OpBne, []int{Reg, Reg, Addr}},
"bgt": {OpBgt, []int{Reg, Reg, Addr}},
"blt": {OpBlt, []int{Reg, Reg, Addr}},
"j": {OpJ, []int{Addr}},
"jr": {OpJr, []int{Reg}},
"call": {OpCall, []int{Addr}},
"exit": {OpExit, []int{}},
}
var lc int
func NewReader(s []Symbol) *Reader {
r := new(Reader)
r.sym = s
return r
}
func NewWriter(w io.Writer) *Writer {
r := new(Writer)
r.lab = make(map[string]uint32)
r.addr = make(map[uint32]string)
r.f = w
return r
}
func (s *Reader) Read() (Symbol, error) {
if s.nsym == len(s.sym) {
return Symbol{}, errors.New("bad argument count")
}
sym := s.sym[s.nsym]
s.nsym++
return sym, nil
}
func (s *Reader) Expect(t int) (Symbol, error) {
sym, err := s.Read()
if err != nil {
return sym, err
}
switch t {
case Id:
if sym.Type != t && sym.Type != Label {
return sym, fmt.Errorf("expected identifier got '%s'", sym.Val)
}
default:
if sym.Type != t && sym.Type != Id {
tval := "register"
if t == Addr {
tval = "immediate"
}
return sym, fmt.Errorf("expected %s got '%s'", tval, sym.Val)
}
}
return sym, nil
}
func (w *Writer) WriteAddr(addr uint32) {
binary.Write(&w.buf, binary.LittleEndian, addr)
w.pc += 4
}
func (w *Writer) WriteSymbol(sym Symbol) error {
switch sym.Type {
case Id:
if f, ok := inst[sym.Val]; ok {
w.buf.WriteByte(f.Op)
w.pc++
} else {
w.addr[w.pc] = sym.Val
w.WriteAddr(0)
}
case Label:
_, ok := w.lab[sym.Val]
if ok {
return fmt.Errorf("redefining label '%s'", sym.Val)
}
w.lab[sym.Val] = w.pc
case Reg:
r, err := strconv.Atoi(sym.Val)
if err != nil {
return fmt.Errorf("bad register '%s'", sym.Val)
}
w.buf.WriteByte(byte(r))
w.pc++
case Addr:
addr, err := strconv.ParseInt(sym.Val, 16, 32)
if err != nil {
return fmt.Errorf("bad address '%s'", sym.Val)
}
w.WriteAddr(uint32(addr))
}
return nil
}
func (w *Writer) Write() (int, error) {
b := w.buf.Bytes()
for i, j := range w.addr {
l, ok := w.lab[j]
if !ok {
return -1, fmt.Errorf("%s: no such label", j)
}
b[i] = byte(l)
b[i+1] = byte(l >> 8)
b[i+2] = byte(l >> 16)
b[i+3] = byte(l >> 24)
}
if n, err := w.f.Write(Hdr); err != nil {
return n, err
}
return w.f.Write(b)
}
func ReadToken(r *bufio.Reader) (string, error) {
b := new(bytes.Buffer)
for {
c, err := r.ReadByte()
if err != nil {
return "", err
}
if c == '\n' {
lc++
}
if unicode.IsSpace(rune(c)) {
break
}
b.WriteByte(c)
}
return b.String(), nil
}
func Read(r *bufio.Reader) (sym Symbol, err error) {
sym.Type = -1
for {
c, err := r.ReadByte()
if err != nil {
sym.Type = Eof
break
}
switch c {
case '\n':
lc++
case '#':
r.ReadBytes('\n')
lc++
return sym, nil
}
if unicode.IsSpace(rune(c)) {
continue
}
if !unicode.IsGraphic(rune(c)) {
return sym, fmt.Errorf("invalid character '%02x'", c)
}
if t, ok := syms[c]; ok {
s, err := ReadToken(r)
if err != nil {
sym.Type = Eof
} else {
sym = Symbol{t, s, lc}
}
break
}
if sym.Type == -1 && unicode.IsLetter(rune(c)) {
r.UnreadByte()
s, err := ReadToken(r)
if err != nil {
sym.Type = Eof
} else {
if s[len(s)-1] == ':' {
sym = Symbol{Label, strings.TrimSuffix(s, ":"), lc}
lc++
} else {
sym = Symbol{Id, s, lc}
}
}
break
}
}
return sym, nil
}
// Gen takes the code from r and writes a machine code representation
// to w. Any errors are outputted to e.
func Gen(r io.Reader, w io.Writer, e io.Writer) (sym []Symbol, err error) {
b := bufio.NewReader(r)
errc := 0
werr := func(s Symbol, err error) {
if errc <= ErrThreshold {
fmt.Fprintf(e, "%d: %s\n", s.Line, err)
}
errc++
}
for {
s, err := Read(b)
if err != nil {
werr(s, err)
}
if errc > ErrThreshold {
return sym, errors.New("invalid file")
}
if s.Type != -1 {
sym = append(sym, s)
}
if s.Type == Eof {
break
}
}
reader := NewReader(sym)
writer := NewWriter(w)
for {
s, err := reader.Expect(Id)
if s.Type == Eof {
break
}
if err != nil {
werr(s, err)
continue
}
if s.Type != Label {
f, ok := inst[s.Val]
if !ok {
werr(s, fmt.Errorf("bad instruction '%s'", s.Val))
continue
}
writer.WriteSymbol(s)
for _, t := range f.Params {
s, err = reader.Expect(t)
if err != nil {
werr(s, err)
} else if err = writer.WriteSymbol(s); err != nil {
werr(s, err)
}
}
continue
}
if err = writer.WriteSymbol(s); err != nil {
werr(s, err)
}
}
if errc > ErrThreshold {
return sym, fmt.Errorf("%d errors (%d shown)", errc, ErrThreshold)
} else if errc > 0 {
return sym, fmt.Errorf("%d errors", errc)
}
if _, err := writer.Write(); err != nil {
fmt.Fprintf(e, "%s\n", err)
}
return sym, nil
}

21
asm/op.go Normal file
View File

@ -0,0 +1,21 @@
package asm
const (
OpNop = iota
OpLd
OpLr
OpSt
OpAdd
OpSub
OpAddi
OpSubi
OpP
OpBeq
OpBne
OpBgt
OpBlt
OpJ
OpJr
OpCall
OpExit
)

35
cmd/hypo/main.go Normal file
View File

@ -0,0 +1,35 @@
package main
import (
"fmt"
"os"
"github.com/rtcall/hypo/cpu"
)
func main() {
if len(os.Args) < 2 {
fmt.Printf("usage: %s file\n", os.Args[0])
return
}
buf, err := os.ReadFile(os.Args[1])
if err != nil {
fmt.Printf("error: %s\n", err)
os.Exit(1)
}
c, err := cpu.New(buf)
if err != nil {
fmt.Printf("error: %s\n", err)
os.Exit(1)
}
for c.State() {
if err := c.Step(); err != nil {
fmt.Printf("fatal: %s\n\n", err)
c.WriteTrace(os.Stdout)
break
}
}
}

46
cmd/hypoc/main.go Normal file
View File

@ -0,0 +1,46 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/rtcall/hypo/asm"
)
func main() {
outPath := flag.String("o", "out", "output path")
flag.Parse()
if len(flag.Args()) == 0 {
fmt.Printf("usage: %s [-o path] file\n", os.Args[0])
os.Exit(1)
}
inPath := flag.Arg(0)
f, err := os.OpenFile(*outPath, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
panic(err)
}
defer f.Close()
f.Truncate(0)
in, err := os.Open(inPath)
if err != nil {
f.Close()
os.Remove(*outPath)
panic(err)
}
defer in.Close()
_, err = asm.Gen(in, f, os.Stderr)
if err != nil {
f.Close()
os.Remove(*outPath)
fmt.Printf("%s: %s\n", inPath, err)
os.Exit(1)
}
}

351
cpu/cpu.go Normal file
View File

@ -0,0 +1,351 @@
package cpu
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/rtcall/hypo/asm"
)
type Cpu struct {
reg [8]uint32
mem [8192]byte
pc uint32
flags uint32
err error
buf *bytes.Reader
}
func New(buf []byte) (c Cpu, err error) {
c.buf = bytes.NewReader(buf)
hdr := make([]byte, len(asm.Hdr))
if _, err = c.buf.Read(hdr); err != nil {
return c, errors.New("could not read header")
} else if !bytes.Equal(hdr, asm.Hdr) {
return c, errors.New("bad header")
}
return c, nil
}
func (c *Cpu) read(ins any) {
if err := binary.Read(c.buf, binary.LittleEndian, ins); err != nil {
c.err = errors.New("bad read")
}
}
func (c *Cpu) checkReg(r byte) error {
if r > byte(len(c.reg)) {
c.err = fmt.Errorf("invalid register %02x", r)
c.flags |= 1
}
return c.err
}
func (c *Cpu) readReg(r byte) uint32 {
if c.checkReg(r) == nil {
return c.reg[r]
}
return 0
}
func (c *Cpu) writeReg(r byte, i uint32) {
if c.checkReg(r) == nil {
c.reg[r] = i
}
}
func (c *Cpu) readImm(addr uint32) (uint32, error) {
if addr > uint32(len(c.mem)) {
return 0, fmt.Errorf("illegal read %08x", addr)
}
i := c.mem[addr:4]
return uint32(i[3])<<24 | uint32(i[2])<<16 | uint32(i[1])<<8 | uint32(i[0]), nil
}
func (c *Cpu) writeImm(addr, imm uint32) error {
if addr > uint32(len(c.mem)) {
return fmt.Errorf("illegal write %08x (at %08x)", imm, addr)
}
c.mem[addr] = byte(imm)
c.mem[addr+1] = byte(imm >> 8)
c.mem[addr+2] = byte(imm >> 16)
c.mem[addr+3] = byte(imm >> 24)
return nil
}
func (c *Cpu) jump(pc uint32) error {
if _, err := c.buf.Seek(int64(pc+uint32(len(asm.Hdr))), io.SeekStart); err != nil {
return err
}
c.pc = pc
return nil
}
var ops = map[byte]func(*Cpu) int{
asm.OpNop: func(*Cpu) int {
return 0
},
asm.OpLd: func(c *Cpu) int {
var ins struct {
R1, R2 byte
}
if c.read(&ins); c.err != nil {
return 0
}
i, err := c.readImm(c.readReg(ins.R2))
c.err = err
if err != nil {
c.writeReg(ins.R1, i)
}
return 2
},
asm.OpLr: func(c *Cpu) int {
var ins struct {
I uint32
R byte
}
if c.read(&ins); c.err != nil {
return 0
}
c.writeReg(ins.R, ins.I)
return 5
},
asm.OpSt: func(c *Cpu) int {
var ins struct {
R1, R2 byte
}
if c.read(&ins); c.err != nil {
return 0
}
c.err = c.writeImm(c.readReg(ins.R1), c.readReg(ins.R2))
return 2
},
asm.OpAdd: func(c *Cpu) int {
var ins struct {
R1, R2, R3 byte
}
if c.read(&ins); c.err != nil {
return 0
}
c.writeReg(ins.R3, c.readReg(ins.R1)+c.readReg(ins.R2))
return 3
},
asm.OpSub: func(c *Cpu) int {
var ins struct {
R1, R2, R3 byte
}
if c.read(&ins); c.err != nil {
return 0
}
c.writeReg(ins.R3, c.readReg(ins.R1)-c.readReg(ins.R2))
return 3
},
asm.OpAddi: func(c *Cpu) int {
var ins struct {
R1 byte
I uint32
R2 byte
}
if c.read(&ins); c.err != nil {
return 0
}
c.writeReg(ins.R2, c.readReg(ins.R1)+ins.I)
return 6
},
asm.OpSubi: func(c *Cpu) int {
var ins struct {
R1 byte
I uint32
R2 byte
}
if c.read(&ins); c.err != nil {
return 0
}
c.writeReg(ins.R2, c.readReg(ins.R1)-ins.I)
return 6
},
asm.OpP: func(c *Cpu) int {
var R byte
if c.read(&R); c.err != nil {
return 0
}
fmt.Print(string(rune(c.reg[R])))
return 1
},
asm.OpBeq: func(c *Cpu) int {
var ins struct {
R1, R2 byte
I uint32
}
if c.read(&ins); c.err != nil {
return 0
}
if c.readReg(ins.R1) == c.readReg(ins.R2) {
c.jump(ins.I)
return 0
}
return 6
},
asm.OpBne: func(c *Cpu) int {
var ins struct {
R1, R2 byte
I uint32
}
if c.read(&ins); c.err != nil {
return 0
}
if c.readReg(ins.R1) != c.readReg(ins.R2) {
c.jump(ins.I)
return 0
}
return 6
},
asm.OpBgt: func(c *Cpu) int {
var ins struct {
R1, R2 byte
I uint32
}
if c.read(&ins); c.err != nil {
return 0
}
if c.readReg(ins.R1) > c.readReg(ins.R2) {
c.jump(ins.I)
return 0
}
return 6
},
asm.OpBlt: func(c *Cpu) int {
var ins struct {
R1, R2 byte
I uint32
}
if c.read(&ins); c.err != nil {
return 0
}
if c.readReg(ins.R1) < c.readReg(ins.R2) {
c.jump(ins.I)
return 0
}
return 6
},
asm.OpJ: func(c *Cpu) int {
var I uint32
if c.read(&I); c.err != nil {
return 0
}
c.jump(I)
return 0
},
asm.OpJr: func(c *Cpu) int {
var R byte
if c.read(&R); c.err != nil {
return 0
}
c.jump(c.readReg(R))
return 0
},
asm.OpCall: func(c *Cpu) int {
var I uint32
if c.read(&I); c.err != nil {
return 0
}
c.writeReg(3, c.pc+4)
c.jump(I)
return 0
},
asm.OpExit: func(c *Cpu) int {
c.flags |= 1
return 0
},
}
func (c *Cpu) State() bool {
return c.flags != 1
}
func (c *Cpu) Step() error {
var op byte
if c.read(&op); c.err != nil {
return c.err
}
c.pc++
f, ok := ops[op]
if !ok {
c.pc--
return fmt.Errorf("invalid opcode: %02x", op)
}
pc := f(c)
c.pc += uint32(pc)
return c.err
}
func (c *Cpu) WriteTrace(w io.Writer) {
fmt.Fprintln(w, "register trace:")
for i, j := range c.reg {
fmt.Fprintf(w, "%02x: %08x\n", i, j)
}
fmt.Fprintf(w, "pc: %08x\n", c.pc)
fmt.Fprintln(w, "memory trace:")
for i, j := range c.mem {
if i > 0xff {
break
}
if i > 0 && i%16 == 0 {
fmt.Fprintln(w, "")
}
fmt.Fprintf(w, "%02x ", j)
}
fmt.Fprintln(w, "")
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/rtcall/hypo
go 1.19

23
sample/l.s Normal file
View File

@ -0,0 +1,23 @@
main:
lr $61 %0 # a
lr $7b %1 # z + 1
loop:
p %0
addi %0 $1 %0
bne %0 %1 loop
nop
lr $0a %0 # \n
p %0
lr $40 %0
call func
exit
func:
lr $0 %1
func_loop:
addi %1 $2 %1
blt %1 %0 func_loop
jr %3