mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-14 02:54:37 -04:00
reorganize things into different packages
This commit is contained in:
169
vm/compiler/bridge/bridge.go
Normal file
169
vm/compiler/bridge/bridge.go
Normal file
@ -0,0 +1,169 @@
|
||||
package bridge
|
||||
|
||||
// #cgo CFLAGS: -I"${SRCDIR}/../../../build/"
|
||||
// #cgo LDFLAGS: "${SRCDIR}/../../../build/libsointu.a"
|
||||
// #include <sointu.h>
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/vsariola/sointu"
|
||||
"github.com/vsariola/sointu/vm"
|
||||
)
|
||||
|
||||
type BridgeService struct {
|
||||
}
|
||||
|
||||
func (s BridgeService) Compile(patch sointu.Patch) (sointu.Synth, error) {
|
||||
synth, err := Synth(patch)
|
||||
return synth, err
|
||||
}
|
||||
|
||||
func Synth(patch sointu.Patch) (*C.Synth, error) {
|
||||
s := new(C.Synth)
|
||||
comPatch, err := vm.Encode(patch, vm.AllFeatures{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error compiling patch: %v", err)
|
||||
}
|
||||
if len(comPatch.Commands) > 2048 { // TODO: 2048 could probably be pulled automatically from cgo
|
||||
return nil, errors.New("bridge supports at most 2048 commands; the compiled patch has more")
|
||||
}
|
||||
if len(comPatch.Values) > 16384 { // TODO: 16384 could probably be pulled automatically from cgo
|
||||
return nil, errors.New("bridge supports at most 16384 values; the compiled patch has more")
|
||||
}
|
||||
for i, v := range comPatch.Commands {
|
||||
s.Commands[i] = (C.uchar)(v)
|
||||
}
|
||||
for i, v := range comPatch.Values {
|
||||
s.Values[i] = (C.uchar)(v)
|
||||
}
|
||||
for i, v := range comPatch.DelayTimes {
|
||||
s.DelayTimes[i] = (C.ushort)(v)
|
||||
}
|
||||
for i, v := range comPatch.SampleOffsets {
|
||||
s.SampleOffsets[i].Start = (C.uint)(v.Start)
|
||||
s.SampleOffsets[i].LoopStart = (C.ushort)(v.LoopStart)
|
||||
s.SampleOffsets[i].LoopLength = (C.ushort)(v.LoopLength)
|
||||
}
|
||||
s.NumVoices = C.uint(comPatch.NumVoices)
|
||||
s.Polyphony = C.uint(comPatch.PolyphonyBitmask)
|
||||
s.RandSeed = 1
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Render renders until the buffer is full or the modulated time is reached, whichever
|
||||
// happens first.
|
||||
// Parameters:
|
||||
// buffer float32 slice to fill with rendered samples. Stereo signal, so
|
||||
// should have even length.
|
||||
// maxtime how long nominal time to render in samples. Speed unit might modulate time
|
||||
// so the actual number of samples rendered depends on the modulation and if
|
||||
// buffer is full before maxtime is reached.
|
||||
// Returns a tuple (int, int, error), consisting of:
|
||||
// samples number of samples rendered in the buffer
|
||||
// time how much the time advanced
|
||||
// error potential error
|
||||
// In practice, if nsamples = len(buffer)/2, then time <= maxtime. If maxtime was reached
|
||||
// first, then nsamples <= len(buffer)/2 and time >= maxtime. Note that it could happen that
|
||||
// time > maxtime, as it is modulated and the time could advance by 2 or more, so the loop
|
||||
// exit condition would fire when the time is already past maxtime.
|
||||
// Under no conditions, nsamples >= len(buffer)/2 i.e. guaranteed to never overwrite the buffer.
|
||||
func (synth *C.Synth) Render(buffer []float32, maxtime int) (int, int, error) {
|
||||
if len(buffer)%1 == 1 {
|
||||
return -1, -1, errors.New("RenderTime writes stereo signals, so buffer should have even length")
|
||||
}
|
||||
samples := C.int(len(buffer) / 2)
|
||||
time := C.int(maxtime)
|
||||
errcode := int(C.su_render(synth, (*C.float)(&buffer[0]), &samples, &time))
|
||||
if errcode > 0 {
|
||||
return int(samples), int(time), &RenderError{errcode: errcode}
|
||||
}
|
||||
return int(samples), int(time), nil
|
||||
}
|
||||
|
||||
// Trigger is part of C.Synths' implementation of sointu.Synth interface
|
||||
func (s *C.Synth) Trigger(voice int, note byte) {
|
||||
if voice < 0 || voice >= len(s.SynthWrk.Voices) {
|
||||
return
|
||||
}
|
||||
s.SynthWrk.Voices[voice] = C.Voice{}
|
||||
s.SynthWrk.Voices[voice].Note = C.int(note)
|
||||
}
|
||||
|
||||
// Release is part of C.Synths' implementation of sointu.Synth interface
|
||||
func (s *C.Synth) Release(voice int) {
|
||||
if voice < 0 || voice >= len(s.SynthWrk.Voices) {
|
||||
return
|
||||
}
|
||||
s.SynthWrk.Voices[voice].Release = 1
|
||||
}
|
||||
|
||||
// Update
|
||||
func (s *C.Synth) Update(patch sointu.Patch) error {
|
||||
comPatch, err := vm.Encode(patch, vm.AllFeatures{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error compiling patch: %v", err)
|
||||
}
|
||||
if len(comPatch.Commands) > 2048 { // TODO: 2048 could probably be pulled automatically from cgo
|
||||
return errors.New("bridge supports at most 2048 commands; the compiled patch has more")
|
||||
}
|
||||
if len(comPatch.Values) > 16384 { // TODO: 16384 could probably be pulled automatically from cgo
|
||||
return errors.New("bridge supports at most 16384 values; the compiled patch has more")
|
||||
}
|
||||
needsRefresh := false
|
||||
for i, v := range comPatch.Commands {
|
||||
if cmdChar := (C.uchar)(v); s.Commands[i] != cmdChar {
|
||||
s.Commands[i] = cmdChar
|
||||
needsRefresh = true // if any of the commands change, we retrigger all units
|
||||
}
|
||||
}
|
||||
for i, v := range comPatch.Values {
|
||||
s.Values[i] = (C.uchar)(v)
|
||||
}
|
||||
for i, v := range comPatch.DelayTimes {
|
||||
s.DelayTimes[i] = (C.ushort)(v)
|
||||
}
|
||||
for i, v := range comPatch.SampleOffsets {
|
||||
s.SampleOffsets[i].Start = (C.uint)(v.Start)
|
||||
s.SampleOffsets[i].LoopStart = (C.ushort)(v.LoopStart)
|
||||
s.SampleOffsets[i].LoopLength = (C.ushort)(v.LoopLength)
|
||||
}
|
||||
s.NumVoices = C.uint(comPatch.NumVoices)
|
||||
s.Polyphony = C.uint(comPatch.PolyphonyBitmask)
|
||||
if needsRefresh {
|
||||
for i := range s.SynthWrk.Voices {
|
||||
// if any of the commands change, we retrigger all units
|
||||
// note that we don't change the notes or release states, just the units
|
||||
for j := range s.SynthWrk.Voices[i].Units {
|
||||
s.SynthWrk.Voices[i].Units[j] = C.Unit{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Render error stores the exact errorcode, which is actually just the x87 FPU flags,
|
||||
// with only the critical failure flags masked. Useful if you are interested exactly
|
||||
// what went wrong with the patch.
|
||||
type RenderError struct {
|
||||
errcode int
|
||||
}
|
||||
|
||||
func (e *RenderError) Error() string {
|
||||
var reasons []string
|
||||
if e.errcode&0x40 != 0 {
|
||||
reasons = append(reasons, "FPU stack over/underflow")
|
||||
}
|
||||
if e.errcode&0x04 != 0 {
|
||||
reasons = append(reasons, "FPU divide by zero")
|
||||
}
|
||||
if e.errcode&0x01 != 0 {
|
||||
reasons = append(reasons, "FPU invalid operation")
|
||||
}
|
||||
if e.errcode&0x3800 != 0 {
|
||||
reasons = append(reasons, "FPU stack push/pops are not balanced")
|
||||
}
|
||||
return "RenderError: " + strings.Join(reasons, ", ")
|
||||
}
|
268
vm/compiler/bridge/bridge_test.go
Normal file
268
vm/compiler/bridge/bridge_test.go
Normal file
@ -0,0 +1,268 @@
|
||||
package bridge_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/vsariola/sointu"
|
||||
"github.com/vsariola/sointu/vm/compiler/bridge"
|
||||
"gopkg.in/yaml.v2"
|
||||
// TODO: test the song using a mocks instead
|
||||
)
|
||||
|
||||
const BPM = 100
|
||||
const SAMPLE_RATE = 44100
|
||||
const TOTAL_ROWS = 16
|
||||
const SAMPLES_PER_ROW = SAMPLE_RATE * 4 * 60 / (BPM * 16)
|
||||
|
||||
const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
|
||||
|
||||
// const bufsize = su_max_samples * 2
|
||||
|
||||
func TestOscillatSine(t *testing.T) {
|
||||
patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
||||
sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||
sointu.Unit{Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": sointu.Sine, "lfo": 0, "unison": 0}},
|
||||
sointu.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}},
|
||||
sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||
sointu.Unit{Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": sointu.Sine, "lfo": 0, "unison": 0}},
|
||||
sointu.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}},
|
||||
sointu.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}},
|
||||
}}}
|
||||
tracks := []sointu.Track{{NumVoices: 1, Order: []int{0}, Patterns: [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}}}}
|
||||
song := sointu.Song{BPM: 100, RowsPerBeat: 4, Score: sointu.Score{RowsPerPattern: 16, Length: 1, Tracks: tracks}, Patch: patch}
|
||||
synth, err := bridge.Synth(patch)
|
||||
if err != nil {
|
||||
t.Fatalf("Compiling patch failed: %v", err)
|
||||
}
|
||||
buffer, err := sointu.Play(synth, song)
|
||||
if err != nil {
|
||||
t.Fatalf("Render failed: %v", err)
|
||||
}
|
||||
compareToRawFloat32(t, buffer, "test_oscillat_sine.raw")
|
||||
}
|
||||
|
||||
func TestRenderSamples(t *testing.T) {
|
||||
patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
||||
sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}},
|
||||
sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 95, "decay": 64, "sustain": 64, "release": 80, "gain": 128}},
|
||||
sointu.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}},
|
||||
}}}
|
||||
|
||||
synth, err := bridge.Synth(patch)
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
}
|
||||
synth.Trigger(0, 64)
|
||||
buffer := make([]float32, 2*su_max_samples)
|
||||
err = sointu.Render(synth, buffer[:len(buffer)/2])
|
||||
if err != nil {
|
||||
t.Fatalf("first render gave an error")
|
||||
}
|
||||
synth.Release(0)
|
||||
err = sointu.Render(synth, buffer[len(buffer)/2:])
|
||||
if err != nil {
|
||||
t.Fatalf("first render gave an error")
|
||||
}
|
||||
compareToRawFloat32(t, buffer, "test_render_samples.raw")
|
||||
}
|
||||
|
||||
func TestAllRegressionTests(t *testing.T) {
|
||||
_, myname, _, _ := runtime.Caller(0)
|
||||
files, err := filepath.Glob(path.Join(path.Dir(myname), "..", "..", "..", "tests", "*.yml"))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot glob files in the test directory: %v", err)
|
||||
}
|
||||
for _, filename := range files {
|
||||
basename := filepath.Base(filename)
|
||||
testname := strings.TrimSuffix(basename, path.Ext(basename))
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
if runtime.GOOS != "windows" && strings.Contains(testname, "sample") {
|
||||
t.Skip("Samples (gm.dls) available only on Windows")
|
||||
return
|
||||
}
|
||||
asmcode, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read the .asm file: %v", filename)
|
||||
}
|
||||
var song sointu.Song
|
||||
err = yaml.Unmarshal(asmcode, &song)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse the .yml file: %v", err)
|
||||
}
|
||||
synth, err := bridge.Synth(song.Patch)
|
||||
if err != nil {
|
||||
t.Fatalf("Compiling patch failed: %v", err)
|
||||
}
|
||||
buffer, err := sointu.Play(synth, song)
|
||||
buffer = buffer[:song.Score.LengthInRows()*song.SamplesPerRow()*2] // extend to the nominal length always.
|
||||
if err != nil {
|
||||
t.Fatalf("Play failed: %v", err)
|
||||
}
|
||||
if os.Getenv("SOINTU_TEST_SAVE_OUTPUT") == "YES" {
|
||||
outputpath := path.Join(path.Dir(myname), "actual_output")
|
||||
if _, err := os.Stat(outputpath); os.IsNotExist(err) {
|
||||
os.Mkdir(outputpath, 0755)
|
||||
}
|
||||
outFileName := path.Join(path.Dir(myname), "actual_output", testname+".raw")
|
||||
outfile, err := os.OpenFile(outFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
defer outfile.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Creating file failed: %v", err)
|
||||
}
|
||||
var createdbuf bytes.Buffer
|
||||
err = binary.Write(&createdbuf, binary.LittleEndian, buffer)
|
||||
if err != nil {
|
||||
t.Fatalf("error converting buffer: %v", err)
|
||||
}
|
||||
_, err = outfile.Write(createdbuf.Bytes())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
compareToRawFloat32(t, buffer, testname+".raw")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackUnderflow(t *testing.T) {
|
||||
patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
}}}
|
||||
synth, err := bridge.Synth(patch)
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
}
|
||||
buffer := make([]float32, 2)
|
||||
err = sointu.Render(synth, buffer)
|
||||
if err == nil {
|
||||
t.Fatalf("rendering should have failed due to stack underflow")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackBalancing(t *testing.T) {
|
||||
patch := sointu.Patch{
|
||||
sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
||||
sointu.Unit{Type: "push", Parameters: map[string]int{}},
|
||||
}}}
|
||||
synth, err := bridge.Synth(patch)
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
}
|
||||
buffer := make([]float32, 2)
|
||||
err = sointu.Render(synth, buffer)
|
||||
if err == nil {
|
||||
t.Fatalf("rendering should have failed due to unbalanced stack push/pop")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackOverflow(t *testing.T) {
|
||||
patch := sointu.Patch{
|
||||
sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
||||
sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
}}}
|
||||
synth, err := bridge.Synth(patch)
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
}
|
||||
buffer := make([]float32, 2)
|
||||
err = sointu.Render(synth, buffer)
|
||||
if err == nil {
|
||||
t.Fatalf("rendering should have failed due to stack overflow, despite balanced push/pops")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDivideByZero(t *testing.T) {
|
||||
patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
||||
sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
sointu.Unit{Type: "invgain", Parameters: map[string]int{"invgain": 0}},
|
||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
}}}
|
||||
synth, err := bridge.Synth(patch)
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
}
|
||||
buffer := make([]float32, 2)
|
||||
err = sointu.Render(synth, buffer)
|
||||
if err == nil {
|
||||
t.Fatalf("rendering should have failed due to divide by zero")
|
||||
}
|
||||
}
|
||||
|
||||
func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "..", "tests", "expected_output", rawname))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read expected: %v", err)
|
||||
}
|
||||
expected := make([]float32, len(expectedb)/4)
|
||||
buf := bytes.NewReader(expectedb)
|
||||
err = binary.Read(buf, binary.LittleEndian, &expected)
|
||||
if err != nil {
|
||||
t.Fatalf("error converting expected buffer: %v", err)
|
||||
}
|
||||
if len(expected) != len(buffer) {
|
||||
t.Fatalf("buffer length mismatch, got %v, expected %v", len(buffer), len(expected))
|
||||
}
|
||||
for i, v := range expected {
|
||||
if math.IsNaN(float64(buffer[i])) || math.Abs(float64(v-buffer[i])) > 1e-6 {
|
||||
t.Fatalf("error bigger than 1e-6 detected, at sample position %v", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func compareToRawInt16(t *testing.T, buffer []int16, rawname string) {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read expected: %v", err)
|
||||
}
|
||||
expected := make([]int16, len(expectedb)/2)
|
||||
buf := bytes.NewReader(expectedb)
|
||||
err = binary.Read(buf, binary.LittleEndian, &expected)
|
||||
if err != nil {
|
||||
t.Fatalf("error converting expected buffer: %v", err)
|
||||
}
|
||||
if len(expected) != len(buffer) {
|
||||
t.Fatalf("buffer length mismatch, got %v, expected %v", len(buffer), len(expected))
|
||||
}
|
||||
for i, v := range expected {
|
||||
if math.IsNaN(float64(buffer[i])) || v != buffer[i] {
|
||||
t.Fatalf("error at sample position %v", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func convertToInt16Buffer(buffer []float32) []int16 {
|
||||
int16Buffer := make([]int16, len(buffer))
|
||||
for i, v := range buffer {
|
||||
int16Buffer[i] = int16(math.Round(math.Min(math.Max(float64(v), -1.0), 1.0) * 32767))
|
||||
}
|
||||
return int16Buffer
|
||||
}
|
8
vm/compiler/bridge/init_windows.go
Normal file
8
vm/compiler/bridge/init_windows.go
Normal file
@ -0,0 +1,8 @@
|
||||
package bridge
|
||||
|
||||
// #include "sointu.h"
|
||||
import "C"
|
||||
|
||||
func init() {
|
||||
C.su_load_gmdls() // GM.DLS is an windows specific sound bank so samples work currently only on windows
|
||||
}
|
144
vm/compiler/compiler.go
Normal file
144
vm/compiler/compiler.go
Normal file
@ -0,0 +1,144 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
"github.com/vsariola/sointu"
|
||||
"github.com/vsariola/sointu/vm"
|
||||
)
|
||||
|
||||
type Compiler struct {
|
||||
Template *template.Template
|
||||
OS string
|
||||
Arch string
|
||||
Output16Bit bool
|
||||
}
|
||||
|
||||
// New returns a new compiler using the default .asm templates
|
||||
func New(os string, arch string, output16Bit bool) (*Compiler, error) {
|
||||
_, myname, _, _ := runtime.Caller(0)
|
||||
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, output16Bit, templateDir)
|
||||
return compiler, err
|
||||
}
|
||||
|
||||
func NewFromTemplates(os string, arch string, output16Bit bool, templateDirectory string) (*Compiler, error) {
|
||||
globPtrn := filepath.Join(templateDirectory, "*.*")
|
||||
tmpl, err := template.New("base").Funcs(sprig.TxtFuncMap()).ParseGlob(globPtrn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`could not create template based on directory "%v": %v`, templateDirectory, err)
|
||||
}
|
||||
return &Compiler{Template: tmpl, OS: os, Arch: arch, Output16Bit: output16Bit}, nil
|
||||
}
|
||||
|
||||
func (com *Compiler) Library() (map[string]string, error) {
|
||||
if com.Arch != "386" && com.Arch != "amd64" {
|
||||
return nil, fmt.Errorf(`compiling as a library is supported only on 386 and amd64 architectures (targeted architecture was %v)`, com.Arch)
|
||||
}
|
||||
templates := []string{"library.asm", "library.h"}
|
||||
features := vm.AllFeatures{}
|
||||
retmap := map[string]string{}
|
||||
for _, templateName := range templates {
|
||||
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)
|
||||
}
|
||||
retmap[extension] = populatedTemplate
|
||||
}
|
||||
return retmap, nil
|
||||
}
|
||||
|
||||
func (com *Compiler) Song(song *sointu.Song) (map[string]string, error) {
|
||||
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"}
|
||||
}
|
||||
features := vm.NecessaryFeaturesFor(song.Patch)
|
||||
retmap := map[string]string{}
|
||||
encodedPatch, err := vm.Encode(song.Patch, features)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`could not encode patch: %v`, err)
|
||||
}
|
||||
patterns, sequences, err := vm.ConstructPatterns(song)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`could not encode song: %v`, err)
|
||||
}
|
||||
for _, templateName := range templates {
|
||||
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
|
||||
*vm.BytePatch
|
||||
Patterns [][]byte
|
||||
Sequences [][]byte
|
||||
PatternLength int
|
||||
SequenceLength int
|
||||
Hold int
|
||||
}{compilerMacros, featureSetMacros, x86Macros, songMacros, encodedPatch, patterns, sequences, len(patterns[0]), len(sequences[0]), 1}
|
||||
populatedTemplate, extension, err = com.compile(templateName, &data)
|
||||
} else if com.Arch == "wasm" {
|
||||
wasmMacros := *NewWasmMacros()
|
||||
data := struct {
|
||||
CompilerMacros
|
||||
FeatureSetMacros
|
||||
WasmMacros
|
||||
SongMacros
|
||||
*vm.BytePatch
|
||||
Patterns [][]byte
|
||||
Sequences [][]byte
|
||||
PatternLength int
|
||||
SequenceLength int
|
||||
Hold int
|
||||
}{compilerMacros, featureSetMacros, wasmMacros, songMacros, encodedPatch, patterns, sequences, len(patterns[0]), len(sequences[0]), 1}
|
||||
populatedTemplate, extension, err = com.compile(templateName, &data)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`could not execute template "%v": %v`, templateName, err)
|
||||
}
|
||||
retmap[extension] = populatedTemplate
|
||||
}
|
||||
return retmap, nil
|
||||
}
|
||||
|
||||
func (com *Compiler) compile(templateName string, data interface{}) (string, string, error) {
|
||||
result := bytes.NewBufferString("")
|
||||
err := com.Template.ExecuteTemplate(result, templateName, data)
|
||||
extension := filepath.Ext(templateName)
|
||||
return result.String(), extension, err
|
||||
}
|
28
vm/compiler/compiler_macros.go
Normal file
28
vm/compiler/compiler_macros.go
Normal 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,
|
||||
}
|
||||
}
|
29
vm/compiler/featureset_macros.go
Normal file
29
vm/compiler/featureset_macros.go
Normal file
@ -0,0 +1,29 @@
|
||||
package compiler
|
||||
|
||||
import "github.com/vsariola/sointu/vm"
|
||||
|
||||
type FeatureSetMacros struct {
|
||||
vm.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
vm/compiler/song_macros.go
Normal file
39
vm/compiler/song_macros.go
Normal 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.Score.LengthInRows()
|
||||
p := SongMacros{Song: s, MaxSamples: maxSamples}
|
||||
trackVoiceNumber := 0
|
||||
for _, t := range s.Score.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 {
|
||||
for _, unit := range instr.Units {
|
||||
if unit.Type == "delay" {
|
||||
total += unit.Parameters["count"] * (1 + unit.Parameters["stereo"])
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%v", total)
|
||||
}
|
50
vm/compiler/wasm_macros.go
Normal file
50
vm/compiler/wasm_macros.go
Normal 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()
|
||||
}
|
408
vm/compiler/x86_macros.go
Normal file
408
vm/compiler/x86_macros.go
Normal file
@ -0,0 +1,408 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/vsariola/sointu/vm"
|
||||
)
|
||||
|
||||
type X86Macros struct {
|
||||
Stacklocs []string
|
||||
Amd64 bool
|
||||
OS string
|
||||
DisableSections bool
|
||||
usesFloatConst map[float32]bool
|
||||
usesIntConst map[int]bool
|
||||
floatConsts []float32
|
||||
intConsts []int
|
||||
calls map[string]bool
|
||||
stackframes map[string][]string
|
||||
features vm.FeatureSet
|
||||
}
|
||||
|
||||
func NewX86Macros(os string, Amd64 bool, features vm.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 *X86Macros) 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 *X86Macros) 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 *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))
|
||||
}
|
||||
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 *X86Macros) PTRSIZE() int {
|
||||
if p.Amd64 {
|
||||
return 8
|
||||
}
|
||||
return 4
|
||||
}
|
||||
|
||||
func (p *X86Macros) DPTR() string {
|
||||
if p.Amd64 {
|
||||
return "dq"
|
||||
}
|
||||
return "dd"
|
||||
}
|
||||
|
||||
func (p *X86Macros) PTRWORD() string {
|
||||
if p.Amd64 {
|
||||
return "qword"
|
||||
}
|
||||
return "dword"
|
||||
}
|
||||
|
||||
func (p *X86Macros) AX() string {
|
||||
if p.Amd64 {
|
||||
return "rax"
|
||||
}
|
||||
return "eax"
|
||||
}
|
||||
|
||||
func (p *X86Macros) BX() string {
|
||||
if p.Amd64 {
|
||||
return "rbx"
|
||||
}
|
||||
return "ebx"
|
||||
}
|
||||
|
||||
func (p *X86Macros) CX() string {
|
||||
if p.Amd64 {
|
||||
return "rcx"
|
||||
}
|
||||
return "ecx"
|
||||
}
|
||||
|
||||
func (p *X86Macros) DX() string {
|
||||
if p.Amd64 {
|
||||
return "rdx"
|
||||
}
|
||||
return "edx"
|
||||
}
|
||||
|
||||
func (p *X86Macros) SI() string {
|
||||
if p.Amd64 {
|
||||
return "rsi"
|
||||
}
|
||||
return "esi"
|
||||
}
|
||||
|
||||
func (p *X86Macros) DI() string {
|
||||
if p.Amd64 {
|
||||
return "rdi"
|
||||
}
|
||||
return "edi"
|
||||
}
|
||||
|
||||
func (p *X86Macros) SP() string {
|
||||
if p.Amd64 {
|
||||
return "rsp"
|
||||
}
|
||||
return "esp"
|
||||
}
|
||||
|
||||
func (p *X86Macros) BP() string {
|
||||
if p.Amd64 {
|
||||
return "rbp"
|
||||
}
|
||||
return "ebp"
|
||||
}
|
||||
|
||||
func (p *X86Macros) WRK() string {
|
||||
return p.BP()
|
||||
}
|
||||
|
||||
func (p *X86Macros) VAL() string {
|
||||
return p.SI()
|
||||
}
|
||||
|
||||
func (p *X86Macros) COM() string {
|
||||
return p.BX()
|
||||
}
|
||||
|
||||
func (p *X86Macros) INP() string {
|
||||
return p.DX()
|
||||
}
|
||||
|
||||
func (p *X86Macros) SaveStack(scope string) string {
|
||||
p.stackframes[scope] = p.Stacklocs
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *X86Macros) 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 *X86Macros) TailCall(funcname string) (string, error) {
|
||||
p.calls[funcname] = true
|
||||
p.stackframes[funcname] = p.Stacklocs
|
||||
return "jmp " + funcname, nil
|
||||
}
|
||||
|
||||
func (p *X86Macros) 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 *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)
|
||||
}
|
||||
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 *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)
|
||||
}
|
||||
} else {
|
||||
if !p.DisableSections {
|
||||
return fmt.Sprintf("section .bss.%v nobits alloc noexec write align=256", name)
|
||||
}
|
||||
}
|
||||
return "section .bss align=256"
|
||||
}
|
||||
|
||||
func (p *X86Macros) Data(label string) string {
|
||||
return fmt.Sprintf("%v\n%v:", p.SectData(label), label)
|
||||
}
|
||||
|
||||
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)
|
||||
} 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 *X86Macros) HasCall(funcname string) bool {
|
||||
return p.calls[funcname]
|
||||
}
|
||||
|
||||
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 *X86Macros) 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 *X86Macros) 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 *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 *X86Macros) 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 *X86Macros) 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 *X86Macros) 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 *X86Macros) 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 *X86Macros) 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 *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 *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 *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")
|
||||
} 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 *X86Macros) 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
|
||||
}
|
Reference in New Issue
Block a user