feat(compiler): Add support for targeting WebAssembly.

The working principle is similar as before with x86, but instead of outputting .asm, it outputs .wat. This can be compiled into .wasm by using the wat2wasm assembler.
This commit is contained in:
Veikko Sariola
2020-12-26 23:16:18 +02:00
parent 7e4bcf18e4
commit e4490faa2e
32 changed files with 2138 additions and 170 deletions

View File

@ -21,7 +21,15 @@ type Compiler struct {
// New returns a new compiler using the default .asm templates
func New(os string, arch string) (*Compiler, error) {
_, myname, _, _ := runtime.Caller(0)
templateDir := filepath.Join(path.Dir(myname), "..", "templates")
var subdir string
if arch == "386" || arch == "amd64" {
subdir = "amd64-386"
} else if arch == "wasm" {
subdir = "wasm"
} else {
return nil, fmt.Errorf("compiler.New failed, because only amd64, 386 and wasm archs are supported (targeted architecture was %v)", arch)
}
templateDir := filepath.Join(path.Dir(myname), "..", "templates", subdir)
compiler, err := NewFromTemplates(os, arch, templateDir)
return compiler, err
}
@ -43,9 +51,16 @@ func (com *Compiler) Library() (map[string]string, error) {
features := AllFeatures{}
retmap := map[string]string{}
for _, templateName := range templates {
macros := NewMacros(*com, features)
macros.Library = true
populatedTemplate, extension, err := com.compile(templateName, macros)
compilerMacros := *NewCompilerMacros(*com)
compilerMacros.Library = true
featureSetMacros := FeatureSetMacros{features}
x86Macros := *NewX86Macros(com.OS, com.Arch == "amd64", features, false)
data := struct {
CompilerMacros
FeatureSetMacros
X86Macros
}{compilerMacros, featureSetMacros, x86Macros}
populatedTemplate, extension, err := com.compile(templateName, &data)
if err != nil {
return nil, fmt.Errorf(`could not execute template "%v": %v`, templateName, err)
}
@ -55,10 +70,15 @@ func (com *Compiler) Library() (map[string]string, error) {
}
func (com *Compiler) Song(song *sointu.Song) (map[string]string, error) {
if com.Arch != "386" && com.Arch != "amd64" {
return nil, fmt.Errorf(`compiling a song player is supported only on 386 and amd64 architectures (targeted architecture was %v)`, com.Arch)
if com.Arch != "386" && com.Arch != "amd64" && com.Arch != "wasm" {
return nil, fmt.Errorf(`compiling a song player is supported only on 386, amd64 and wasm architectures (targeted architecture was %v)`, com.Arch)
}
var templates []string
if com.Arch == "386" || com.Arch == "amd64" {
templates = []string{"player.asm", "player.h"}
} else if com.Arch == "wasm" {
templates = []string{"player.wat"}
}
templates := []string{"player.asm", "player.h"}
features := NecessaryFeaturesFor(song.Patch)
retmap := map[string]string{}
encodedPatch, err := Encode(&song.Patch, features)
@ -66,8 +86,32 @@ func (com *Compiler) Song(song *sointu.Song) (map[string]string, error) {
return nil, fmt.Errorf(`could not encode patch: %v`, err)
}
for _, templateName := range templates {
macros := NewPlayerMacros(*com, features, song, encodedPatch)
populatedTemplate, extension, err := com.compile(templateName, macros)
compilerMacros := *NewCompilerMacros(*com)
featureSetMacros := FeatureSetMacros{features}
songMacros := *NewSongMacros(song)
var populatedTemplate, extension string
var err error
if com.Arch == "386" || com.Arch == "amd64" {
x86Macros := *NewX86Macros(com.OS, com.Arch == "amd64", features, false)
data := struct {
CompilerMacros
FeatureSetMacros
X86Macros
SongMacros
*EncodedPatch
}{compilerMacros, featureSetMacros, x86Macros, songMacros, encodedPatch}
populatedTemplate, extension, err = com.compile(templateName, &data)
} else if com.Arch == "wasm" {
wasmMacros := *NewWasmMacros()
data := struct {
CompilerMacros
FeatureSetMacros
WasmMacros
SongMacros
*EncodedPatch
}{compilerMacros, featureSetMacros, wasmMacros, songMacros, encodedPatch}
populatedTemplate, extension, err = com.compile(templateName, &data)
}
if err != nil {
return nil, fmt.Errorf(`could not execute template "%v": %v`, templateName, err)
}

View File

@ -0,0 +1,28 @@
package compiler
import (
"github.com/vsariola/sointu"
)
type CompilerMacros struct {
Clip bool
Library bool
Sine int // TODO: how can we elegantly access global constants in template, without wrapping each one by one
Trisaw int
Pulse int
Gate int
Sample int
Compiler
}
func NewCompilerMacros(c Compiler) *CompilerMacros {
return &CompilerMacros{
Sine: sointu.Sine,
Trisaw: sointu.Trisaw,
Pulse: sointu.Pulse,
Gate: sointu.Gate,
Sample: sointu.Sample,
Compiler: c,
}
}

View File

@ -0,0 +1,27 @@
package compiler
type FeatureSetMacros struct {
FeatureSet
}
func (p *FeatureSetMacros) HasOp(instruction string) bool {
_, ok := p.Opcode(instruction)
return ok
}
func (p *FeatureSetMacros) GetOp(instruction string) int {
v, _ := p.Opcode(instruction)
return v
}
func (p *FeatureSetMacros) Stereo(unitType string) bool {
return p.SupportsParamValue(unitType, "stereo", 1)
}
func (p *FeatureSetMacros) Mono(unitType string) bool {
return p.SupportsParamValue(unitType, "stereo", 0)
}
func (p *FeatureSetMacros) StereoAndMono(unitType string) bool {
return p.Stereo(unitType) && p.Mono(unitType)
}

39
compiler/song_macros.go Normal file
View File

@ -0,0 +1,39 @@
package compiler
import (
"fmt"
"github.com/vsariola/sointu"
)
type SongMacros struct {
Song *sointu.Song
VoiceTrackBitmask int
MaxSamples int
}
func NewSongMacros(s *sointu.Song) *SongMacros {
maxSamples := s.SamplesPerRow() * s.TotalRows()
p := SongMacros{Song: s, MaxSamples: maxSamples}
trackVoiceNumber := 0
for _, t := range s.Tracks {
for b := 0; b < t.NumVoices-1; b++ {
p.VoiceTrackBitmask += 1 << trackVoiceNumber
trackVoiceNumber++
}
trackVoiceNumber++ // set all bits except last one
}
return &p
}
func (p *SongMacros) NumDelayLines() string {
total := 0
for _, instr := range p.Song.Patch.Instruments {
for _, unit := range instr.Units {
if unit.Type == "delay" {
total += unit.Parameters["count"] * (1 + unit.Parameters["stereo"])
}
}
}
return fmt.Sprintf("%v", total)
}

50
compiler/wasm_macros.go Normal file
View File

@ -0,0 +1,50 @@
package compiler
import (
"bytes"
"encoding/binary"
)
type WasmMacros struct {
data *bytes.Buffer
Labels map[string]int
}
func NewWasmMacros() *WasmMacros {
return &WasmMacros{
data: new(bytes.Buffer),
Labels: map[string]int{},
}
}
func (wm *WasmMacros) SetLabel(label string) string {
wm.Labels[label] = wm.data.Len()
return ""
}
func (wm *WasmMacros) GetLabel(label string) int {
return wm.Labels[label]
}
func (wm *WasmMacros) DataB(value byte) string {
binary.Write(wm.data, binary.LittleEndian, value)
return ""
}
func (wm *WasmMacros) DataW(value uint16) string {
binary.Write(wm.data, binary.LittleEndian, value)
return ""
}
func (wm *WasmMacros) DataD(value uint32) string {
binary.Write(wm.data, binary.LittleEndian, value)
return ""
}
func (wm *WasmMacros) ToByte(value int) byte {
return byte(value)
}
func (wm *WasmMacros) Data() []byte {
return wm.data.Bytes()
}

View File

@ -4,74 +4,36 @@ import (
"fmt"
"math"
"strings"
"github.com/vsariola/sointu"
)
type OplistEntry struct {
Type string
NumParams int
}
type Macros struct {
type X86Macros struct {
Stacklocs []string
Output16Bit bool
Clip bool
Library bool
Amd64 bool
OS string
DisableSections bool
Sine int // TODO: how can we elegantly access global constants in template, without wrapping each one by one
Trisaw int
Pulse int
Gate int
Sample int
usesFloatConst map[float32]bool
usesIntConst map[int]bool
floatConsts []float32
intConsts []int
calls map[string]bool
stackframes map[string][]string
FeatureSet
Compiler
features FeatureSet
}
func NewMacros(c Compiler, f FeatureSet) *Macros {
return &Macros{
calls: map[string]bool{},
usesFloatConst: map[float32]bool{},
usesIntConst: map[int]bool{},
stackframes: map[string][]string{},
Sine: sointu.Sine,
Trisaw: sointu.Trisaw,
Pulse: sointu.Pulse,
Gate: sointu.Gate,
Sample: sointu.Sample,
Amd64: c.Arch == "amd64",
Compiler: c,
FeatureSet: f,
func NewX86Macros(os string, Amd64 bool, features FeatureSet, DisableSections bool) *X86Macros {
return &X86Macros{
calls: map[string]bool{},
usesFloatConst: map[float32]bool{},
usesIntConst: map[int]bool{},
stackframes: map[string][]string{},
Amd64: Amd64,
OS: os,
DisableSections: DisableSections,
features: features,
}
}
func (p *Macros) HasOp(instruction string) bool {
_, ok := p.Opcode(instruction)
return ok
}
func (p *Macros) Stereo(unitType string) bool {
return p.SupportsParamValue(unitType, "stereo", 1)
}
func (p *Macros) Mono(unitType string) bool {
return p.SupportsParamValue(unitType, "stereo", 0)
}
func (p *Macros) StereoAndMono(unitType string) bool {
return p.Stereo(unitType) && p.Mono(unitType)
}
// Macros and functions to accumulate constants automagically
func (p *Macros) Float(value float32) string {
func (p *X86Macros) Float(value float32) string {
if _, ok := p.usesFloatConst[value]; !ok {
p.usesFloatConst[value] = true
p.floatConsts = append(p.floatConsts, value)
@ -79,7 +41,7 @@ func (p *Macros) Float(value float32) string {
return nameForFloat(value)
}
func (p *Macros) Int(value int) string {
func (p *X86Macros) Int(value int) string {
if _, ok := p.usesIntConst[value]; !ok {
p.usesIntConst[value] = true
p.intConsts = append(p.intConsts, value)
@ -87,7 +49,7 @@ func (p *Macros) Int(value int) string {
return nameForInt(value)
}
func (p *Macros) Constants() string {
func (p *X86Macros) Constants() string {
var b strings.Builder
for _, v := range p.floatConsts {
fmt.Fprintf(&b, "%-23s dd 0x%x\n", nameForFloat(v), math.Float32bits(v))
@ -110,105 +72,105 @@ func nameForInt(value int) string {
return "ICONST_" + fmt.Sprintf("%d", value)
}
func (p *Macros) PTRSIZE() int {
func (p *X86Macros) PTRSIZE() int {
if p.Amd64 {
return 8
}
return 4
}
func (p *Macros) DPTR() string {
func (p *X86Macros) DPTR() string {
if p.Amd64 {
return "dq"
}
return "dd"
}
func (p *Macros) PTRWORD() string {
func (p *X86Macros) PTRWORD() string {
if p.Amd64 {
return "qword"
}
return "dword"
}
func (p *Macros) AX() string {
func (p *X86Macros) AX() string {
if p.Amd64 {
return "rax"
}
return "eax"
}
func (p *Macros) BX() string {
func (p *X86Macros) BX() string {
if p.Amd64 {
return "rbx"
}
return "ebx"
}
func (p *Macros) CX() string {
func (p *X86Macros) CX() string {
if p.Amd64 {
return "rcx"
}
return "ecx"
}
func (p *Macros) DX() string {
func (p *X86Macros) DX() string {
if p.Amd64 {
return "rdx"
}
return "edx"
}
func (p *Macros) SI() string {
func (p *X86Macros) SI() string {
if p.Amd64 {
return "rsi"
}
return "esi"
}
func (p *Macros) DI() string {
func (p *X86Macros) DI() string {
if p.Amd64 {
return "rdi"
}
return "edi"
}
func (p *Macros) SP() string {
func (p *X86Macros) SP() string {
if p.Amd64 {
return "rsp"
}
return "esp"
}
func (p *Macros) BP() string {
func (p *X86Macros) BP() string {
if p.Amd64 {
return "rbp"
}
return "ebp"
}
func (p *Macros) WRK() string {
func (p *X86Macros) WRK() string {
return p.BP()
}
func (p *Macros) VAL() string {
func (p *X86Macros) VAL() string {
return p.SI()
}
func (p *Macros) COM() string {
func (p *X86Macros) COM() string {
return p.BX()
}
func (p *Macros) INP() string {
func (p *X86Macros) INP() string {
return p.DX()
}
func (p *Macros) SaveStack(scope string) string {
func (p *X86Macros) SaveStack(scope string) string {
p.stackframes[scope] = p.Stacklocs
return ""
}
func (p *Macros) Call(funcname string) (string, error) {
func (p *X86Macros) Call(funcname string) (string, error) {
p.calls[funcname] = true
var s = make([]string, len(p.Stacklocs))
copy(s, p.Stacklocs)
@ -216,13 +178,13 @@ func (p *Macros) Call(funcname string) (string, error) {
return "call " + funcname, nil
}
func (p *Macros) TailCall(funcname string) (string, error) {
func (p *X86Macros) TailCall(funcname string) (string, error) {
p.calls[funcname] = true
p.stackframes[funcname] = p.Stacklocs
return "jmp " + funcname, nil
}
func (p *Macros) SectText(name string) string {
func (p *X86Macros) SectText(name string) string {
if p.OS == "windows" {
if p.DisableSections {
return "section .code align=1"
@ -238,7 +200,7 @@ func (p *Macros) SectText(name string) string {
}
}
func (p *Macros) SectData(name string) string {
func (p *X86Macros) SectData(name string) string {
if p.OS == "windows" || p.OS == "darwin" {
if p.OS == "windows" && !p.DisableSections {
return fmt.Sprintf("section .%v data align=1", name)
@ -252,7 +214,7 @@ func (p *Macros) SectData(name string) string {
}
}
func (p *Macros) SectBss(name string) string {
func (p *X86Macros) SectBss(name string) string {
if p.OS == "windows" || p.OS == "darwin" {
if p.OS == "windows" && !p.DisableSections {
return fmt.Sprintf("section .%v bss align=256", name)
@ -266,11 +228,11 @@ func (p *Macros) SectBss(name string) string {
}
}
func (p *Macros) Data(label string) string {
func (p *X86Macros) Data(label string) string {
return fmt.Sprintf("%v\n%v:", p.SectData(label), label)
}
func (p *Macros) Func(funcname string, scope ...string) (string, error) {
func (p *X86Macros) Func(funcname string, scope ...string) (string, error) {
scopeName := funcname
if len(scope) > 1 {
return "", fmt.Errorf(`Func macro "%v" can take only one additional scope parameter, "%v" were given`, funcname, scope)
@ -281,16 +243,16 @@ func (p *Macros) Func(funcname string, scope ...string) (string, error) {
return fmt.Sprintf("%v\n%v:", p.SectText(funcname), funcname), nil
}
func (p *Macros) HasCall(funcname string) bool {
func (p *X86Macros) HasCall(funcname string) bool {
return p.calls[funcname]
}
func (p *Macros) Push(value string, name string) string {
func (p *X86Macros) Push(value string, name string) string {
p.Stacklocs = append(p.Stacklocs, name)
return fmt.Sprintf("push %v ; Stack: %v ", value, p.FmtStack())
}
func (p *Macros) PushRegs(params ...string) string {
func (p *X86Macros) PushRegs(params ...string) string {
if p.Amd64 {
var b strings.Builder
for i := 0; i < len(params); i = i + 2 {
@ -312,7 +274,7 @@ func (p *Macros) PushRegs(params ...string) string {
}
}
func (p *Macros) PopRegs(params ...string) string {
func (p *X86Macros) PopRegs(params ...string) string {
if p.Amd64 {
var b strings.Builder
for i := len(params) - 1; i >= 0; i-- {
@ -338,13 +300,13 @@ func (p *Macros) PopRegs(params ...string) string {
}
}
func (p *Macros) Pop(register string) string {
func (p *X86Macros) Pop(register string) string {
last := p.Stacklocs[len(p.Stacklocs)-1]
p.Stacklocs = p.Stacklocs[:len(p.Stacklocs)-1]
return fmt.Sprintf("pop %v ; %v = %v, Stack: %v ", register, register, last, p.FmtStack())
}
func (p *Macros) SaveFPUState() string {
func (p *X86Macros) SaveFPUState() string {
i := 0
for ; i < 108; i += p.PTRSIZE() {
p.Stacklocs = append(p.Stacklocs, fmt.Sprintf("F%v", i))
@ -352,7 +314,7 @@ func (p *Macros) SaveFPUState() string {
return fmt.Sprintf("sub %[1]v, %[2]v\nfsave [%[1]v]", p.SP(), i)
}
func (p *Macros) LoadFPUState() string {
func (p *X86Macros) LoadFPUState() string {
i := 0
for ; i < 108; i += p.PTRSIZE() {
p.Stacklocs = p.Stacklocs[:len(p.Stacklocs)-1]
@ -360,7 +322,7 @@ func (p *Macros) LoadFPUState() string {
return fmt.Sprintf("frstor [%[1]v]\nadd %[1]v, %[2]v", p.SP(), i)
}
func (p *Macros) Stack(name string) (string, error) {
func (p *X86Macros) Stack(name string) (string, error) {
for i, k := range p.Stacklocs {
if k == name {
pos := len(p.Stacklocs) - i - 1
@ -378,7 +340,7 @@ func (p *Macros) Stack(name string) (string, error) {
return "", fmt.Errorf("unknown symbol %v", name)
}
func (p *Macros) FmtStack() string {
func (p *X86Macros) FmtStack() string {
var b strings.Builder
last := len(p.Stacklocs) - 1
for i := range p.Stacklocs {
@ -390,7 +352,7 @@ func (p *Macros) FmtStack() string {
return b.String()
}
func (p *Macros) ExportFunc(name string, params ...string) string {
func (p *X86Macros) ExportFunc(name string, params ...string) string {
if !p.Amd64 {
reverseParams := make([]string, len(params))
for i, param := range params {
@ -407,20 +369,20 @@ func (p *Macros) ExportFunc(name string, params ...string) string {
return fmt.Sprintf("%[1]v\nglobal %[2]v\n%[2]v:", p.SectText(name), name)
}
func (p *Macros) Input(unit string, port string) (string, error) {
i := p.InputNumber(unit, port)
func (p *X86Macros) Input(unit string, port string) (string, error) {
i := p.features.InputNumber(unit, port)
if i != 0 {
return fmt.Sprintf("%v + %v", p.INP(), i*4), nil
}
return p.INP(), nil
}
func (p *Macros) Modulation(unit string, port string) (string, error) {
i := p.InputNumber(unit, port)
func (p *X86Macros) Modulation(unit string, port string) (string, error) {
i := p.features.InputNumber(unit, port)
return fmt.Sprintf("%v + %v", p.WRK(), i*4+32), nil
}
func (p *Macros) Prepare(value string, regs ...string) (string, error) {
func (p *X86Macros) Prepare(value string, regs ...string) (string, error) {
if p.Amd64 {
if len(regs) > 1 {
return "", fmt.Errorf("macro Prepare cannot accept more than one register parameter")
@ -432,7 +394,7 @@ func (p *Macros) Prepare(value string, regs ...string) (string, error) {
return "", nil
}
func (p *Macros) Use(value string, regs ...string) (string, error) {
func (p *X86Macros) Use(value string, regs ...string) (string, error) {
if p.Amd64 {
return "r9", nil
}
@ -443,39 +405,3 @@ func (p *Macros) Use(value string, regs ...string) (string, error) {
}
return value, nil
}
type PlayerMacros struct {
Song *sointu.Song
VoiceTrackBitmask int
MaxSamples int
Macros
EncodedPatch
}
func NewPlayerMacros(c Compiler, f FeatureSet, s *sointu.Song, e *EncodedPatch) *PlayerMacros {
maxSamples := s.SamplesPerRow() * s.TotalRows()
macros := *NewMacros(c, f)
macros.Output16Bit = s.Output16Bit // TODO: should we actually store output16bit in Songs or not?
p := PlayerMacros{Song: s, Macros: macros, MaxSamples: maxSamples, EncodedPatch: *e}
trackVoiceNumber := 0
for _, t := range s.Tracks {
for b := 0; b < t.NumVoices-1; b++ {
p.VoiceTrackBitmask += 1 << trackVoiceNumber
trackVoiceNumber++
}
trackVoiceNumber++ // set all bits except last one
}
return &p
}
func (p *PlayerMacros) NumDelayLines() string {
total := 0
for _, instr := range p.Song.Patch.Instruments {
for _, unit := range instr.Units {
if unit.Type == "delay" {
total += unit.Parameters["count"] * (1 + unit.Parameters["stereo"])
}
}
}
return fmt.Sprintf("%v", total)
}