wriwriwriting

This commit is contained in:
2024-07-11 03:36:56 +00:00
parent 5da03f07f1
commit 972748e0af
5 changed files with 1777 additions and 9 deletions

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -208,10 +208,14 @@ Currently, it has been proved that BDA can be converted to a general-purpose fun
Also, Kronos\cite{norilo2015} and W-calculus\cite{arias2021} are examples of attempts at lambda-calculus-based abstraction, while being influenced by Faust. Kronos is based on the theoretical foundation of System-$F\omega$, a lambda computation in which the type itself is the object of the lambda abstraction (a function can be defined that computes the type and returns a new type). The W-calculus limits the systems it represents to linear time-invariant ones and defines a more formal semantics, aiming at automatic proofs of the linearlity and an identity of graph topologies.
Previously, the author designed a programming language for music \textit{mimium} \cite{Matsuura2021}. It adds the basic operations of delay and feedback to lambda-calculus, signal processing can be expressed concisely while having a syntax close to that of general-purpose programming languages.
Previously, the author designed a programming language for music \textit{mimium} \cite{matsuura2021a}. It adds the basic operations of delay and feedback to lambda-calculus, signal processing can be expressed concisely while having a syntax close to that of general-purpose programming languages(especially, the syntax of mimium is designed to be looks like Rust language).
In this paper, we present the syntax and semantics of a computational model: \lambdammm, which is an intermediate representation of mimium based on W-calculus. We also propose a virtual machine and its instruction set for efficient execution of this computational model.
One of the previous issues with mimium was the inability to compile the codes which contains a combination of recursive or higher-order functions and stateful functions because the compiler can not be determine the data size of the internal state of the signal processing.
In this paper, the syntax and semantics of \lambdammm, a superset of the W-calculus-based call-by-value simply-typed lambda calculus are explained, as a computation model that is supposed to be an intermediate representation of mimium. Also a virtual machine and its instruction set are proposed to execute this computation model practically. Based on this model, it is possible to describe and execute generative signal processing in mimium.
\section{Syntax}
\label{sec:syntax}
@@ -292,7 +296,7 @@ VM Instructions for \lambdammm differs from Lua VM in the following respects.
\end{enumerate}
Instructions in \lambdammm VM are 32bit tagged-union data that has up to 3 operands. Currently, a bit width for tag and each operands are all 8 bit\footnote[1]{Reason for this is easy to implemented on \textrm{enum} data structure on Rust, a host language of the latest mimium compiler.}.
Instructions in \lambdammm VM are 32bit tagged-union data that has up to 3 operands. Currently, a bit width for tag and each operands are all 8 bit\footnote[1]{Reason for this is that it is easy to implemented on \textrm{enum} data structure on Rust, a host language of the latest mimium compiler. Operands bitwidth and alignment may be changed in the future.}.
The VM of \lambdammm is a register machine like the Lua VM(after version 5, although the VM has no real register but the register number simply means the offset index of call stack from the base pointer at the point of execution of the VM). The first operand of most instructions is the register number in which to store the result of the calculation.
@@ -313,18 +317,24 @@ SETUPVALUE & A B & U(B) := R(A) \\
GETSTATE & A & R(A) := SPtr[SPos] \\
SETSTATE & A & Sptr[SPos] := R(A) \\
SHIFTSTATE & sAx & SPos += sAx \\
DELAY & A B & R(A) := update\_ringbuffer(SPtr[SPos],R(B)) \\
JMP & sAx & PC +=sAx \\
JMPIFNEG & A sBx & if (R(A)<0) then PC += sBx \\
CALL & A B C & R(A), ... ,R(A+C-2) := program.functions[R(A)](R(A+1),...,R(A+B-1)) \\
CALLCLS & A B C & Sptr := vm.closures[R(A)].Sptr; \\
\ & \ & R(A), ... ,R(A+C-2) := vm.closures[R(A)].fnproto(R(A+1),...,R(A+B-1)); \\
\ & \ & Sptr := global\_sptr \\
CALL & A B C & R(A),...,R(A+C-2) := program.functions[R(A)](R(A+1),...,R(A+B-1)) \\
CALLCLS & A B C & Sptr := vm.closures[R(A)].Sptr \\
\ & \ & R(A),...,R(A+C-2) := vm.closures[R(A)].fnproto(R(A+1),...,R(A+B-1)) \\
\ & \ & Sptr := vm.global\_sptr \\
CLOSURE & A Bx & vm.closures.push(closure(program.functions[R(Bx)])) \\
& & R(A) := vm.closures.length - 1 \\
CLOSE & A & close stack variables up to R(A)\\
RETURN & A B & return R(A), R(A+1)...,R(A+B-2) \\
ADDF & A B C & R(A) := R(B) as float + R(C) as float\\
SUBF & A B C & R(A) := R(B) as float - R(C) as float\\
MULF & A B C & R(A) := R(B) as float * R(C) as float\\
DIVF & A B C & R(A) := R(B) as float / R(C) as float\\
\multicolumn{3}{l}{
ADDF & A B C & R(A) := R(B) as int + R(C) as in \\
\ &
\multicolumn{2}{l}{
\textit{...Other basic arithmetics continues for each primitive types...}
}
\end{tabular}
@@ -354,6 +364,25 @@ SETSTATE 2 // store to self
RETURN 3 1
\end{lstlisting}
\subsection{Overview of the VM structure}
\label{sec:vmstructure}
The overview of a data structure of the VM, program and the instantiated closure for \lambdammm is shown in \ref{fig:vmstructure}. In addition to the normal call stack, the VM has a storage area for managing internal state data for feedback and delay.
This storage area is accompanied by data indicating the position from which the internal state is retrieved by the \texttt{GETSTATE} / \texttt{SETSTATE} instructions. This position is modified by \texttt{SHIFTSTATE} operation. The the actual data in the memory area are statically layed out at compile time by analyzing function calls that include references to self, delay and the functions which will call such statefull functions recursively.
However, in the case of higher-order functions that receive a function as an argument and return another function, the layout of the internal state of the receiving function is unknown at the compilation, so an internal state storage area is created for each instantiated closure separately from the global storage area held by the VM itself. The VM switches the \texttt{State\_Ptr}, which points the internal state storage to be used, at each closure call, to the storage area on the closure, and returns a pointer pointing to the global storage area each time the closure context ends.
Instantiated closures also hold the storage area of Upvalues. Until the closure exits the context of the parent function (Open Closure), Upvalues holds a negative offset on the stack at the current execution. Because this offset value can be determined at compile time, stored in the function prototype in the program.
When the closure escapes from the original function with \texttt{RETURN} instruction, inserted \texttt{CLOSE} instruction before the return, which causes, Actual upvalues are moved from stack to somewhere on the heap memory. This Upvalue may be referenced from multiple locations when using nested closures, and some form of garbage collection needed to free memory after it is no longer referred.
\begin{figure*}[ht]
\centerline{\includegraphics[width=\hsize]{lambdammm_vm_structure}}
\caption{\label{fig:vmstructure}{\it Overview of the virtual machine, program and instantiated closures for \lambdammm.}}
\end{figure*}
\section{Discussion}
\label{sec:discussion}
@@ -365,7 +394,7 @@ Because mimium treats functions that have internal states which change over time
An example is the higher-order function \texttt{filterbank}, which duplicates \texttt{filter} function \texttt{N} times parametrically, and mix them together. Listing.\ref{lst:filterbank_bad} is an example of an incorrect code, and Listing.\ref{lst:filterbank_good} is an example of the code that behave correctly. The difference between Listing.\ref{lst:filterbank_bad} and Listing.\ref{lst:filterbank_good} is that the recursive calls in the filterbank function are written directly in the inner function to be returned, and the recursive calls in the filterbank function are written with \texttt{let} binding out of the inner function\footnote[2]{In the previous specification of mimium in \cite{matsuura2021a}, the binding of new variable and destructive assignment were the same syntax(\texttt{x = a}) but the syntax for the variable binding has changed to use \texttt{let} keyword.}. Similarly, in the \texttt{dsp} function that will be called by the audio driver, the difference is whether the filterbank function is executed inside \texttt{dsp} or bound with \texttt{let} once in the global context.
In the case of normal functional language, if all the functions used in a composition do not contain destructive assignments, the calculation process will not change even if the variable bound by let is manually replaced with its term(Beta reduction), as in the conversion from Listing.\ref{lst:filterbank_good} to Listing.\ref{lst:filterbank_bad}.
In the case of normal functional language, if all the functions used in a composition do not contain destructive assignments, the calculation process will not change even if the variable bound by \texttt{let} is manually replaced with its term(beta reduction), as in the conversion from Listing.\ref{lst:filterbank_good} to Listing.\ref{lst:filterbank_bad}.
But in mimium, there are two major stages of evaluation: the code is evaluated in the global environment (finalization of the signal processing graph) at first, then the dsp function is repeatedly executed (actual signal processing), and the function may involve implicit internal state updates. Therefore, even though the code does not include destructive assignments, the recursive execution of the \texttt{filterbank} function is performed only once in Listing.\ref{lst:filterbank_good} for the evaluation of the global environment, whereas in Listing.\ref{lst:filterbank_bad}, every sample the dsp function is executed, the recursive function is executed and a closure is generated. Since the initialization of the internal state in the closure is performed at the time of closure allocation, in the example of Listing\ref{lst:filterbank_bad}, the internal state of the closure after the evaluation of \texttt{filterbank} is reset at each time step.

View File

@@ -44,6 +44,7 @@ e \; ::=& \ x &x \in {v_p} \ & [value]\\
|& \ let\; x = e_1\; in\; e_2 & & [let]\\
|& \ fix \; x.e & & [fixpoint]\\
|& \ e_1 \; e_2 & & [app]\\
|& \ if\; (e_c)\; e_t\; else\; e_e \; & & [if] \\
|& \ delay\; n \; e_1 \; e_2 &n \in \mathbb{N}\ & [delay]\\
|& \ feed \; x.e & & [feed]\\
%%|& \quad (e_1,e_2) \quad & [product]\\

View File

@@ -15,13 +15,20 @@
\end{equation*}\textrm{T-DELAY}
\end{minipage}\\
\begin{minipage}[t]{0.45\hsize}
\begin{minipage}[t]{0.4\hsize}
\centering
\begin{equation*}
\frac{\Gamma, x : \tau_p \vdash e: \tau_p }{\Gamma \vdash feed\ x.e:\tau_p}
\end{equation*}\textrm{T-FEED}
\end{minipage}&
\begin{minipage}[t]{0.45\hsize}
\centering
\begin{equation*}
\frac{ \Gamma \vdash e_c : R\ \Gamma \vdash e_t:\tau\ \Gamma \vdash e_e:\tau }{\Gamma \vdash if\ (e_c)\ e_t\ e_e\ : \tau}
\end{equation*}\textrm{T-IF}
\end{minipage}\\
\end{tabular}
\caption{\label{fig:typing}{\it Typing rules for lambda abstraction and feed abstraction in $\lambda_{mmm}$.}}
\end{figure}