diff --git a/templates/wasm/output_sound.wat b/templates/wasm/output_sound.wat index 028e13a..83b651a 100644 --- a/templates/wasm/output_sound.wat +++ b/templates/wasm/output_sound.wat @@ -1,5 +1,5 @@ {{- if not .Output16Bit }} - (i64.store (global.get $outputBufPtr) (i64.load (i32.const 4128))) ;; load the sample from left & right channels as one 64bit int and store it in the address pointed by outputBufPtr + (i64.store (global.get $outputBufPtr) (i64.load (i32.const {{index .Labels "su_globalports"}}))) ;; load the sample from left & right channels as one 64bit int and store it in the address pointed by outputBufPtr (global.set $outputBufPtr (i32.add (global.get $outputBufPtr) (i32.const 8))) ;; advance outputbufptr {{- else }} (local.set $channel (i32.const 0)) @@ -7,7 +7,7 @@ (i32.store16 (global.get $outputBufPtr) (i32.trunc_f32_s (f32.mul (call $clip - (f32.load offset=4128 (i32.mul (local.get $channel) (i32.const 4))) + (f32.load offset={{index .Labels "su_globalports"}} (i32.mul (local.get $channel) (i32.const 4))) ) (f32.const 32767) ) @@ -16,4 +16,4 @@ (br_if $channelLoop (local.tee $channel (i32.eqz (local.get $channel)))) end {{- end }} - (i64.store (i32.const 4128) (i64.const 0)) ;; clear the left and right ports \ No newline at end of file + (i64.store (i32.const {{index .Labels "su_globalports"}}) (i64.const 0)) ;; clear the left and right ports \ No newline at end of file diff --git a/templates/wasm/patch.wat b/templates/wasm/patch.wat index ffc82d1..44b3fff 100644 --- a/templates/wasm/patch.wat +++ b/templates/wasm/patch.wat @@ -13,7 +13,7 @@ {{- $addr := sub (index .Labels "su_vm_transformcounts") 1}} (if (i32.lt_u (local.get $paramNum) (i32.load8_u offset={{$addr}} (local.get $opcode)))(then ;;(i32.ge (local.get $paramNum) (i32.load8_u (local.get $opcode))) /*TODO: offset to transformvalues (local.set $WRKplusparam (i32.add (global.get $WRK) (local.get $paramX4))) - (f32.store offset=512 + (f32.store offset={{index .Labels "su_transformedvalues"}} (local.get $paramX4) (f32.add (f32.mul @@ -59,7 +59,7 @@ ;; The transformed values start at 512 (TODO: change magic constants somehow) ;;------------------------------------------------------------------------------- (func $input (param $inputNumber i32) (result f32) - (f32.load offset=512 (i32.mul (local.get $inputNumber) (i32.const 4))) + (f32.load offset={{index .Labels "su_transformedvalues"}} (i32.mul (local.get $inputNumber) (i32.const 4))) ) ;;------------------------------------------------------------------------------- diff --git a/templates/wasm/player.wat b/templates/wasm/player.wat index 2fa2f36..42d538c 100644 --- a/templates/wasm/player.wat +++ b/templates/wasm/player.wat @@ -5,7 +5,7 @@ ; Patterns ;------------------------------------------------------------------------------- */}} -{{- .SetLabel "su_patterns"}} +{{- .SetDataLabel "su_patterns"}} {{- $m := .}} {{- range .Patterns}} {{- range .}} @@ -18,7 +18,7 @@ ; Tracks ;------------------------------------------------------------------------------- */}} -{{- .SetLabel "su_tracks"}} +{{- .SetDataLabel "su_tracks"}} {{- $m := .}} {{- range .Sequences}} {{- range .}} @@ -31,7 +31,7 @@ ; The code for this patch, basically indices to vm jump table ;------------------------------------------------------------------------------- */}} -{{- .SetLabel "su_patch_code"}} +{{- .SetDataLabel "su_patch_code"}} {{- range .Commands}} {{- $.DataB .}} {{- end}} @@ -41,7 +41,7 @@ ; The parameters / inputs to each opcode ;------------------------------------------------------------------------------- */}} -{{- .SetLabel "su_patch_parameters"}} +{{- .SetDataLabel "su_patch_parameters"}} {{- range .Values}} {{- $.DataB .}} {{- end}} @@ -51,7 +51,7 @@ ; Delay times ;------------------------------------------------------------------------------- */}} -{{- .SetLabel "su_delay_times"}} +{{- .SetDataLabel "su_delay_times"}} {{- range .DelayTimes}} {{- $.DataW .}} {{- end}} @@ -61,11 +61,61 @@ ; The number of transformed parameters each opcode takes ;------------------------------------------------------------------------------- */}} -{{- .SetLabel "su_vm_transformcounts"}} +{{- .SetDataLabel "su_vm_transformcounts"}} {{- range .Instructions}} {{- $.TransformCount . | $.ToByte | $.DataB}} {{- end}} +{{- /* +;------------------------------------------------------------------------------- +; Allocate memory for stack. +; Stack of 64 float signals is enough for everybody... right? +; Note: as the stack grows _downwards_ the label is _after_ stack +;------------------------------------------------------------------------------- +*/}} +{{- .Align}} +{{- .Block 256}} +{{- .SetBlockLabel "su_stack"}} + +{{- /* +;------------------------------------------------------------------------------- +; Allocate memory for transformed values. +;------------------------------------------------------------------------------- +*/}} +{{- .Align}} +{{- .SetBlockLabel "su_transformedvalues"}} +{{- .Block 32}} + +{{- /* +;------------------------------------------------------------------------------- +; Uninitialized memory for synth, delaylines & outputbuffer +;------------------------------------------------------------------------------- +*/}} +{{- .Align}} +{{- if ne .VoiceTrackBitmask 0}} +{{- .SetBlockLabel "su_trackcurrentvoice"}} +{{- .Block 32}} +{{- end}} +{{- .Align}} +{{- .SetBlockLabel "su_synth"}} +{{- .Block 32}} +{{- .SetBlockLabel "su_globalports"}} +{{- .Block 32}} +{{- .SetBlockLabel "su_voices"}} +{{- .Block 131072}} +{{- .Align}} +{{- .SetBlockLabel "su_delaylines"}} +{{- .Block (int (mul 262156 .Song.Patch.NumDelayLines))}} +{{- .Align}} +{{- .SetBlockLabel "su_outputbuffer"}} +{{- if .Output16Bit}} +{{- .Block (int (mul .PatternLength .SequenceLength .Song.SamplesPerRow 4))}} +{{- else}} +{{- .Block (int (mul .PatternLength .SequenceLength .Song.SamplesPerRow 8))}} +{{- end}} +{{- .SetBlockLabel "su_outputend"}} + + ;;------------------------------------------------------------------------------ ;; Import the difficult math functions from javascript ;; (seriously now, it's 2020) @@ -82,9 +132,8 @@ ;;------------------------------------------------------------------------------ ;; The one and only memory -;; TODO: Its size should be calculated just to fit, but not more ;;------------------------------------------------------------------------------ -(memory (export "m") 256) +(memory (export "m") {{.MemoryPages}}) ;;------------------------------------------------------------------------------ ;; Globals. Putting all with same initialization value should compress most @@ -106,11 +155,11 @@ (global $voice (mut i32) (i32.const 0)) (global $voicesRemain (mut i32) (i32.const 0)) (global $randseed (mut i32) (i32.const 1)) -(global $sp (mut i32) (i32.const 2048)) -(global $outputBufPtr (mut i32) (i32.const 8388608)) +(global $sp (mut i32) (i32.const {{index .Labels "su_stack"}})) +(global $outputBufPtr (mut i32) (i32.const {{index .Labels "su_outputbuffer"}})) ;; TODO: only export start and length with certain compiler options; in demo use, they can be hard coded ;; in the intro -(global $outputStart (export "s") i32 (i32.const 8388608)) ;; TODO: do not hard code, layout memory somehow intelligently +(global $outputStart (export "s") i32 (i32.const {{index .Labels "su_outputbuffer"}})) (global $outputLength (export "l") i32 (i32.const {{if .Output16Bit}}{{mul .PatternLength .SequenceLength .Song.SamplesPerRow 4}}{{else}}{{mul .PatternLength .SequenceLength .Song.SamplesPerRow 8}}{{end}})) (global $output16bit (export "t") i32 (i32.const {{if .Output16Bit}}1{{else}}0{{end}})) @@ -168,13 +217,11 @@ (global.set $COM_instr_start (global.get $COM)) (global.set $VAL_instr_start (global.get $VAL)) {{- end}} - (global.set $WRK (i32.const 4160)) - (global.set $voice (i32.const 4160)) + (global.set $WRK (i32.const {{index .Labels "su_voices"}})) + (global.set $voice (i32.const {{index .Labels "su_voices"}})) (global.set $voicesRemain (i32.const {{.Song.Patch.NumVoices | printf "%v"}})) {{- if .HasOp "delay"}} - (global.set $delayWRK (i32.const 262144)) ;; BAD IDEA: we are limited to something like 30 delay lines - ;; after that, the delay lines start to overwrite the outputbuffer. Find a way to layout the memory - ;; based on the song, instead of hard coding addressed. + (global.set $delayWRK (i32.const {{index .Labels "su_delaylines"}})) {{- end}} (call $su_run_vm) {{- template "output_sound.wat" .}} @@ -217,10 +264,10 @@ (i32.load8_u offset={{index .Labels "su_patterns"}}) (local.tee $note) (if (i32.ne (i32.const {{.Hold}}))(then - (i32.store offset=4164 + (i32.store offset={{add (index .Labels "su_voices") 4}} (i32.mul (i32.add - (local.tee $voiceNo (i32.load8_u offset=768 (local.get $tracksRemaining))) + (local.tee $voiceNo (i32.load8_u offset={{index .Labels "su_trackcurrentvoice"}} (local.get $tracksRemaining))) (local.get $firstVoice) ) (i32.const 4096) @@ -239,11 +286,11 @@ ) (i32.const 4096) ) - (i32.const 4160) + (i32.const {{index .Labels "su_voices"}}) )) (memory.fill (local.get $di) (i32.const 0) (i32.const 4096)) (i32.store (local.get $di) (local.get $note)) - (i32.store8 offset=768 (local.get $tracksRemaining) (local.get $voiceNo)) + (i32.store8 offset={{index .Labels "su_trackcurrentvoice"}} (local.get $tracksRemaining) (local.get $voiceNo)) )) )) (local.set $si (i32.add (local.get $si) (i32.const {{.SequenceLength}}))) @@ -256,7 +303,7 @@ (func $su_update_voices (local $si i32) (local $di i32) (local $tracksRemaining i32) (local $note i32) (local.set $tracksRemaining (i32.const {{len .Sequences}})) (local.set $si (global.get $pattern)) - (local.set $di (i32.const 4160)) + (local.set $di (i32.const {{index .Labels "su_voices"}})) loop $track_loop (i32.load8_u offset={{index .Labels "su_tracks"}} (local.get $si)) (i32.mul (i32.const {{.PatternLength}})) diff --git a/templates/wasm/sinks.wat b/templates/wasm/sinks.wat index 7d3a83d..3e3c53a 100644 --- a/templates/wasm/sinks.wat +++ b/templates/wasm/sinks.wat @@ -6,7 +6,7 @@ ;; Stereo: also add outgain*ST1 to main right port and auxgain*ST1 to aux1 right ;;------------------------------------------------------------------------------- (func $su_op_outaux (param $stereo i32) (local $addr i32) - (local.set $addr (i32.const 4128)) + (local.set $addr (i32.const {{index .Labels "su_globalports"}})) {{- if .Stereo "outaux"}} loop $stereoLoop {{- end}} @@ -47,7 +47,7 @@ ;; Stereo: also add gain*ST1 to right port ;;------------------------------------------------------------------------------- (func $su_op_aux (param $stereo i32) (local $addr i32) - (local.set $addr (i32.add (i32.mul (call $scanValueByte) (i32.const 4)) (i32.const 4128))) + (local.set $addr (i32.add (i32.mul (call $scanValueByte) (i32.const 4)) (i32.const {{index .Labels "su_globalports"}}))) {{- if .Stereo "aux"}} loop $stereoLoop {{- end}} @@ -97,7 +97,7 @@ (local.set $scaledAddress (i32.add (i32.mul (i32.and (local.get $address) (i32.const 0x7FF7)) (i32.const 4)) {{- if .SupportsGlobalSend}} (select - (i32.const 4096) + (i32.const {{index .Labels "su_synth"}}) {{- end}} (global.get $voice) {{- if .SupportsGlobalSend}} @@ -137,7 +137,7 @@ {{- end}} ;;------------------------------------------------------------------------------- (func $su_op_out (param $stereo i32) (local $ptr i32) - (local.set $ptr (i32.const 4128)) ;; synth.left, but should not be magic constant + (local.set $ptr (i32.const {{index .Labels "su_globalports"}})) ;; synth.left (f32.store (local.get $ptr) (f32.add (f32.mul @@ -147,7 +147,7 @@ (f32.load (local.get $ptr)) ) ) - (local.set $ptr (i32.const 4132)) ;; synth.right, but should not be magic constant + (local.set $ptr (i32.const {{add (index .Labels "su_globalports") 4}})) ;; synth.right, note that ATM does not seem to support mono ocpode at all (f32.store (local.get $ptr) (f32.add (f32.mul diff --git a/templates/wasm/sources.wat b/templates/wasm/sources.wat index cec12c6..d83edb7 100644 --- a/templates/wasm/sources.wat +++ b/templates/wasm/sources.wat @@ -183,7 +183,7 @@ {{- if .SupportsParamValueOtherThan "oscillator" "unison" 0}} (call $push (f32.add (call $pop) (call $pop))) (if (local.tee $unison (i32.sub (local.get $unison) (i32.const 1)))(then - (f32.store offset={{.InputNumber "oscillator" "phase" | mul 4 | add 512}} (i32.const 0) + (f32.store offset={{.InputNumber "oscillator" "phase" | mul 4 | add (index .Labels "su_transformedvalues")}} (i32.const 0) (f32.add (call $input (i32.const {{.InputNumber "oscillator" "phase"}})) (f32.const 0.08333333) ;; 1/12, add small phase shift so all oscillators don't start in phase @@ -328,7 +328,7 @@ {{- if .Stereo "in"}} (i32.add (local.get $stereo)) ;; start from right channel if stereo {{- end}} - (local.set $addr (i32.add (i32.mul (i32.const 4)) (i32.const 4128))) + (local.set $addr (i32.add (i32.mul (i32.const 4)) (i32.const {{index .Labels "su_globalports"}}))) {{- if .Stereo "in"}} loop $stereoLoop {{- end}} diff --git a/vm/compiler/wasm_macros.go b/vm/compiler/wasm_macros.go index 92db1e5..f7341c9 100644 --- a/vm/compiler/wasm_macros.go +++ b/vm/compiler/wasm_macros.go @@ -5,39 +5,70 @@ import ( "encoding/binary" ) +// WasmMacros are the macros called from .wat templates +// +// NOTE! Due to the single pass nature of the compilation and the way memory is +// organized, you should initialize all initialized data in the .wat files using +// DataB, DataW and DataD macros _before_ any calls to Block. Block allocates +// uninitialized data blocks from the memory. type WasmMacros struct { - data *bytes.Buffer - Labels map[string]int + data *bytes.Buffer + blockStart int + blockAlign int + Labels map[string]int } func NewWasmMacros() *WasmMacros { return &WasmMacros{ - data: new(bytes.Buffer), - Labels: map[string]int{}, + data: new(bytes.Buffer), + blockAlign: 128, + Labels: map[string]int{}, } } -func (wm *WasmMacros) SetLabel(label string) string { +func (wm *WasmMacros) SetDataLabel(label string) string { wm.Labels[label] = wm.data.Len() return "" } +func (wm *WasmMacros) SetBlockLabel(label string) string { + wm.Labels[label] = wm.blockStart + return "" +} + +func (wm *WasmMacros) Align() string { + wm.blockStart += wm.blockAlign - 1 - ((wm.blockStart + wm.blockAlign - 1) % wm.blockAlign) + return "" +} + +func (wm *WasmMacros) MemoryPages() int { + return (wm.blockStart + 65535) / 65536 +} + 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) + wm.blockStart++ return "" } func (wm *WasmMacros) DataW(value uint16) string { binary.Write(wm.data, binary.LittleEndian, value) + wm.blockStart += 2 return "" } func (wm *WasmMacros) DataD(value uint32) string { binary.Write(wm.data, binary.LittleEndian, value) + wm.blockStart += 4 + return "" +} + +func (wm *WasmMacros) Block(value int) string { + wm.blockStart += value return "" }