mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
Play depends on bridge and compile on compiler package. Before, the compiler depended on bridge, but we could not use the compiler to build the library, as the bridge depends on the library. Also, play can now start having slightly more options e.g. wav out etc.
482 lines
11 KiB
Go
482 lines
11 KiB
Go
package compiler
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
|
|
"github.com/vsariola/sointu"
|
|
)
|
|
|
|
type OplistEntry struct {
|
|
Type string
|
|
NumParams int
|
|
}
|
|
|
|
type Macros struct {
|
|
Stacklocs []string
|
|
Output16Bit bool
|
|
Clip bool
|
|
Library bool
|
|
Amd64 bool
|
|
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
|
|
}
|
|
|
|
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 (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 {
|
|
if _, ok := p.usesFloatConst[value]; !ok {
|
|
p.usesFloatConst[value] = true
|
|
p.floatConsts = append(p.floatConsts, value)
|
|
}
|
|
return nameForFloat(value)
|
|
}
|
|
|
|
func (p *Macros) Int(value int) string {
|
|
if _, ok := p.usesIntConst[value]; !ok {
|
|
p.usesIntConst[value] = true
|
|
p.intConsts = append(p.intConsts, value)
|
|
}
|
|
return nameForInt(value)
|
|
}
|
|
|
|
func (p *Macros) Constants() string {
|
|
var b strings.Builder
|
|
for _, v := range p.floatConsts {
|
|
fmt.Fprintf(&b, "%-23s dd 0x%x\n", nameForFloat(v), math.Float32bits(v))
|
|
}
|
|
for _, v := range p.intConsts {
|
|
fmt.Fprintf(&b, "%-23s dd 0x%x\n", nameForInt(v), v)
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func nameForFloat(value float32) string {
|
|
s := fmt.Sprintf("%#g", value)
|
|
s = strings.Replace(s, ".", "_", 1)
|
|
s = strings.Replace(s, "-", "m", 1)
|
|
s = strings.Replace(s, "+", "p", 1)
|
|
return "FCONST_" + s
|
|
}
|
|
|
|
func nameForInt(value int) string {
|
|
return "ICONST_" + fmt.Sprintf("%d", value)
|
|
}
|
|
|
|
func (p *Macros) PTRSIZE() int {
|
|
if p.Amd64 {
|
|
return 8
|
|
}
|
|
return 4
|
|
}
|
|
|
|
func (p *Macros) DPTR() string {
|
|
if p.Amd64 {
|
|
return "dq"
|
|
}
|
|
return "dd"
|
|
}
|
|
|
|
func (p *Macros) PTRWORD() string {
|
|
if p.Amd64 {
|
|
return "qword"
|
|
}
|
|
return "dword"
|
|
}
|
|
|
|
func (p *Macros) AX() string {
|
|
if p.Amd64 {
|
|
return "rax"
|
|
}
|
|
return "eax"
|
|
}
|
|
|
|
func (p *Macros) BX() string {
|
|
if p.Amd64 {
|
|
return "rbx"
|
|
}
|
|
return "ebx"
|
|
}
|
|
|
|
func (p *Macros) CX() string {
|
|
if p.Amd64 {
|
|
return "rcx"
|
|
}
|
|
return "ecx"
|
|
}
|
|
|
|
func (p *Macros) DX() string {
|
|
if p.Amd64 {
|
|
return "rdx"
|
|
}
|
|
return "edx"
|
|
}
|
|
|
|
func (p *Macros) SI() string {
|
|
if p.Amd64 {
|
|
return "rsi"
|
|
}
|
|
return "esi"
|
|
}
|
|
|
|
func (p *Macros) DI() string {
|
|
if p.Amd64 {
|
|
return "rdi"
|
|
}
|
|
return "edi"
|
|
}
|
|
|
|
func (p *Macros) SP() string {
|
|
if p.Amd64 {
|
|
return "rsp"
|
|
}
|
|
return "esp"
|
|
}
|
|
|
|
func (p *Macros) BP() string {
|
|
if p.Amd64 {
|
|
return "rbp"
|
|
}
|
|
return "ebp"
|
|
}
|
|
|
|
func (p *Macros) WRK() string {
|
|
return p.BP()
|
|
}
|
|
|
|
func (p *Macros) VAL() string {
|
|
return p.SI()
|
|
}
|
|
|
|
func (p *Macros) COM() string {
|
|
return p.BX()
|
|
}
|
|
|
|
func (p *Macros) INP() string {
|
|
return p.DX()
|
|
}
|
|
|
|
func (p *Macros) SaveStack(scope string) string {
|
|
p.stackframes[scope] = p.Stacklocs
|
|
return ""
|
|
}
|
|
|
|
func (p *Macros) Call(funcname string) (string, error) {
|
|
p.calls[funcname] = true
|
|
var s = make([]string, len(p.Stacklocs))
|
|
copy(s, p.Stacklocs)
|
|
p.stackframes[funcname] = s
|
|
return "call " + funcname, nil
|
|
}
|
|
|
|
func (p *Macros) 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 {
|
|
if p.OS == "windows" {
|
|
if p.DisableSections {
|
|
return "section .code align=1"
|
|
}
|
|
return fmt.Sprintf("section .%v code align=1", name)
|
|
} else if p.OS == "darwin" {
|
|
return "section .text align=1"
|
|
} else {
|
|
if p.DisableSections {
|
|
return "section .text. progbits alloc exec nowrite align=1"
|
|
}
|
|
return fmt.Sprintf("section .text.%v progbits alloc exec nowrite align=1", name)
|
|
}
|
|
}
|
|
|
|
func (p *Macros) 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)
|
|
}
|
|
return "section .data align=1"
|
|
} else {
|
|
if !p.DisableSections {
|
|
return fmt.Sprintf("section .data.%v progbits alloc noexec write align=1", name)
|
|
}
|
|
return "section .data. progbits alloc exec nowrite align=1"
|
|
}
|
|
}
|
|
|
|
func (p *Macros) 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)
|
|
}
|
|
return "section .bss align=256"
|
|
} else {
|
|
if !p.DisableSections {
|
|
return fmt.Sprintf("section .bss.%v progbits alloc noexec write align=256", name)
|
|
}
|
|
return "section .bss. progbits alloc exec nowrite align=256"
|
|
}
|
|
}
|
|
|
|
func (p *Macros) Data(label string) string {
|
|
return fmt.Sprintf("%v\n%v:", p.SectData(label), label)
|
|
}
|
|
|
|
func (p *Macros) 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)
|
|
} else if len(scope) > 0 {
|
|
scopeName = scope[0]
|
|
}
|
|
p.Stacklocs = append(p.stackframes[scopeName], "retaddr_"+funcname)
|
|
return fmt.Sprintf("%v\n%v:", p.SectText(funcname), funcname), nil
|
|
}
|
|
|
|
func (p *Macros) HasCall(funcname string) bool {
|
|
return p.calls[funcname]
|
|
}
|
|
|
|
func (p *Macros) 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 {
|
|
if p.Amd64 {
|
|
var b strings.Builder
|
|
for i := 0; i < len(params); i = i + 2 {
|
|
b.WriteRune('\n')
|
|
b.WriteString(p.Push(params[i], params[i+1]))
|
|
}
|
|
return b.String()
|
|
} else {
|
|
var pushadOrder = [...]string{"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"}
|
|
for _, name := range pushadOrder {
|
|
for j := 0; j < len(params); j = j + 2 {
|
|
if params[j] == name {
|
|
name = params[j+1]
|
|
}
|
|
}
|
|
p.Stacklocs = append(p.Stacklocs, name)
|
|
}
|
|
return fmt.Sprintf("\npushad ; Stack: %v", p.FmtStack())
|
|
}
|
|
}
|
|
|
|
func (p *Macros) PopRegs(params ...string) string {
|
|
if p.Amd64 {
|
|
var b strings.Builder
|
|
for i := len(params) - 1; i >= 0; i-- {
|
|
b.WriteRune('\n')
|
|
b.WriteString(p.Pop(params[i]))
|
|
}
|
|
return b.String()
|
|
} else {
|
|
var regs = [...]string{"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"}
|
|
var b strings.Builder
|
|
for i, name := range p.Stacklocs[len(p.Stacklocs)-8:] {
|
|
if i > 0 {
|
|
b.WriteString(", ")
|
|
}
|
|
b.WriteString(regs[i])
|
|
if regs[i] != name {
|
|
b.WriteString(" = ")
|
|
b.WriteString(name)
|
|
}
|
|
}
|
|
p.Stacklocs = p.Stacklocs[:len(p.Stacklocs)-8]
|
|
return fmt.Sprintf("\npopad ; Popped: %v. Stack: %v", b.String(), p.FmtStack())
|
|
}
|
|
}
|
|
|
|
func (p *Macros) 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 {
|
|
i := 0
|
|
for ; i < 108; i += p.PTRSIZE() {
|
|
p.Stacklocs = append(p.Stacklocs, fmt.Sprintf("F%v", i))
|
|
}
|
|
return fmt.Sprintf("sub %[1]v, %[2]v\nfsave [%[1]v]", p.SP(), i)
|
|
}
|
|
|
|
func (p *Macros) LoadFPUState() string {
|
|
i := 0
|
|
for ; i < 108; i += p.PTRSIZE() {
|
|
p.Stacklocs = p.Stacklocs[:len(p.Stacklocs)-1]
|
|
}
|
|
return fmt.Sprintf("frstor [%[1]v]\nadd %[1]v, %[2]v", p.SP(), i)
|
|
}
|
|
|
|
func (p *Macros) Stack(name string) (string, error) {
|
|
for i, k := range p.Stacklocs {
|
|
if k == name {
|
|
pos := len(p.Stacklocs) - i - 1
|
|
if p.Amd64 {
|
|
pos = pos * 8
|
|
} else {
|
|
pos = pos * 4
|
|
}
|
|
if pos != 0 {
|
|
return fmt.Sprintf("%v + %v", p.SP(), pos), nil
|
|
}
|
|
return p.SP(), nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("unknown symbol %v", name)
|
|
}
|
|
|
|
func (p *Macros) FmtStack() string {
|
|
var b strings.Builder
|
|
last := len(p.Stacklocs) - 1
|
|
for i := range p.Stacklocs {
|
|
if i > 0 {
|
|
b.WriteString(", ")
|
|
}
|
|
b.WriteString(p.Stacklocs[last-i])
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func (p *Macros) ExportFunc(name string, params ...string) string {
|
|
if !p.Amd64 {
|
|
reverseParams := make([]string, len(params))
|
|
for i, param := range params {
|
|
reverseParams[len(params)-1-i] = param
|
|
}
|
|
p.Stacklocs = append(reverseParams, "retaddr_"+name) // in 32-bit, we use stdcall and parameters are in the stack
|
|
if p.OS == "windows" {
|
|
return fmt.Sprintf("%[1]v\nglobal _%[2]v@%[3]v\n_%[2]v@%[3]v:", p.SectText(name), name, len(params)*4)
|
|
}
|
|
}
|
|
if p.OS == "darwin" {
|
|
return fmt.Sprintf("%[1]v\nglobal _%[2]v\n_%[2]v:", p.SectText(name), name)
|
|
}
|
|
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)
|
|
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)
|
|
return fmt.Sprintf("%v + %v", p.WRK(), i*4+32), nil
|
|
}
|
|
|
|
func (p *Macros) 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")
|
|
} else if len(regs) > 0 {
|
|
return fmt.Sprintf("\nmov r9, qword %v\nlea r9, [r9 + %v]", value, regs[0]), nil
|
|
}
|
|
return fmt.Sprintf("\nmov r9, qword %v", value), nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
func (p *Macros) Use(value string, regs ...string) (string, error) {
|
|
if p.Amd64 {
|
|
return "r9", nil
|
|
}
|
|
if len(regs) > 1 {
|
|
return "", fmt.Errorf("macro Use cannot accept more than one register parameter")
|
|
} else if len(regs) > 0 {
|
|
return value + " + " + regs[0], nil
|
|
}
|
|
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)
|
|
}
|