Minor fix
This commit is contained in:
@@ -9,13 +9,13 @@
|
|||||||
|
|
||||||
Many programming languages for sound and music have been developed, but only a few possess strongly formalized semantics. One language that is both rigorously formalized and practical is Faust \cite{Orlarey2004}, which combines blocks with inputs and outputs with five primitive operations: parallel, sequential, split, merge, and recursive connection. By providing basic arithmetic, conditionals, and delays as primitive blocks, almost any type of signal processing can be written in Faust. In a later extension, a macro based on a term rewriting system was introduced, allowing users to parameterize blocks with an arbitrary number of inputs and outputs \cite{graf2010}.
|
Many programming languages for sound and music have been developed, but only a few possess strongly formalized semantics. One language that is both rigorously formalized and practical is Faust \cite{Orlarey2004}, which combines blocks with inputs and outputs with five primitive operations: parallel, sequential, split, merge, and recursive connection. By providing basic arithmetic, conditionals, and delays as primitive blocks, almost any type of signal processing can be written in Faust. In a later extension, a macro based on a term rewriting system was introduced, allowing users to parameterize blocks with an arbitrary number of inputs and outputs \cite{graf2010}.
|
||||||
|
|
||||||
This strong abstraction capability through formalization enables Faust to be translated to various backends, such as C, C++, Rust, and LLVM IR. On the other hand, Faust's Block Diagram Algebra (BDA) lacks theoretical and practical compatibility with common programming languages. Although it is possible to call external C functions in Faust, those functions are assumed to be pure functions that do not require heap memory allocation or deallocation. Therefore, while it is easy to embed Faust in another language, it is not easy to call another language from Faust.
|
This strong abstraction capability through formalization enables Faust to be translated to various backends, such as C, C++, Rust, and LLVM IR. On the other hand, Faust's Block Diagram Algebra (BDA) lacks theoretical and practical compatibility with common programming languages. Although it is possible to call external C functions in Faust, those functions are assumed to be pure functions that do not have any internal states. Therefore, while it is easy to embed Faust in another language, it is not easy to call another language from Faust.
|
||||||
|
|
||||||
In addition, a macro for Faust is an independent term rewriting system that generates BDA based on pattern matching. As a result, the arguments for pattern matching are implicitly required to be integers, which can sometimes lead to compile-time errors, despite the fact that BDA does not distinguish between real and integer types. These implicit typing rules are not intuitive for novice users.
|
In addition, a macro for Faust is an independent term rewriting system that generates BDA based on pattern matching. As a result, the numeric arguments for pattern matching are implicitly required to be integers, which can sometimes lead to compile-time errors, despite the fact that BDA does not distinguish between real and integer types. These implicit typing rules are not intuitive for novice users.
|
||||||
|
|
||||||
Proposing a computational model for signal processing based on more generic computational models, such as lambda calculus, has the potential to enable interoperability between many different general-purpose languages, and also facilitate the appropriation of existing optimization methods and the implementation of compilers and run-time.
|
Proposing a computational model for signal processing based on more generic computational models, such as lambda calculus, has the potential to enable interoperability between many different general-purpose languages, and also facilitate the appropriation of existing optimization methods and the implementation of compilers and run-time.
|
||||||
|
|
||||||
Currently, it has been demonstrated that BDA can be converted to a general-purpose functional language using an arrow, a higher-level abstraction of monads \cite{gaster2018}. However, higher-order functions in general-purpose functional languages are often implemented based on dynamic memory allocation and deallocation, making them difficult to use in host languages designed for real-time signal processing.
|
Currently, it has been demonstrated that BDA can be converted to a general-purpose functional language using an arrow, a higher-level abstraction of monads \cite{gaster2018}. However, higher-order functions in general-purpose functional languages are often implemented with dynamic memory allocation and deallocation, making them difficult to use in host languages designed for real-time signal processing.
|
||||||
|
|
||||||
Additionally, Kronos \cite{norilo2015} and W-calculus \cite{arias2021} are examples of lambda calculus-based abstractions influenced by Faust. Kronos is based on the theoretical foundation of System-$F\omega$, a variation of lambda calculus in which types themselves can be abstracted (i.e., a function that takes a type as input and returns a new type can be defined). In Kronos, type calculations correspond to signal graph generation, while value calculations correspond to the actual processing. Delay is the only special primitive operation in Kronos, and feedback routing can be represented as a recursive function application in type calculations.
|
Additionally, Kronos \cite{norilo2015} and W-calculus \cite{arias2021} are examples of lambda calculus-based abstractions influenced by Faust. Kronos is based on the theoretical foundation of System-$F\omega$, a variation of lambda calculus in which types themselves can be abstracted (i.e., a function that takes a type as input and returns a new type can be defined). In Kronos, type calculations correspond to signal graph generation, while value calculations correspond to the actual processing. Delay is the only special primitive operation in Kronos, and feedback routing can be represented as a recursive function application in type calculations.
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
\section{VM Model and Instruction Set}
|
\section{VM Model and Instruction Set}
|
||||||
\label{sec:vm}
|
\label{sec:vm}
|
||||||
|
|
||||||
The virtual machine model and its instruction set for running \lambdammm\ are based on the Lua version 5 VM \cite{ierusalimschy2005}.
|
The virtual machine model and its instruction set for running \\ \lambdammm\ are based on the Lua version 5 VM \cite{ierusalimschy2005}.
|
||||||
|
|
||||||
When executing a computational model based on lambda calculus, a key challenge is handling the data structure known as a closure. A closure captures the variable environment in which the inner function is defined, allowing it to refer to variables from the outer function’s context. If the inner function is paired with a dictionary of variable names and values, the compiler (or interpreter) implementation is straightforward, but runtime performance is limited.
|
When executing a computational model based on lambda calculus, a key challenge is handling the data structure known as a closure. A closure captures the variable environment in which the inner function is defined, allowing it to refer to variables from the outer function’s context. If the inner function is paired with a dictionary of variable names and values, the compiler (or interpreter) implementation is straightforward, but runtime performance is limited.
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
|
|
||||||
The VM for \lambdammm\ operates as a register machine, similar to the Lua VM (post version 5). However, unlike traditional register machines, it does not employ physical registers; instead, the register number simply refers to an offset index on the call stack relative to the base pointer during VM execution. The first operand of most instructions specifies the register number where the result of the operation will be stored.
|
The VM for \lambdammm\ operates as a register machine, similar to the Lua VM (post version 5). However, unlike traditional register machines, it does not employ physical registers; instead, the register number simply refers to an offset index on the call stack relative to the base pointer during VM execution. The first operand of most instructions specifies the register number where the result of the operation will be stored.
|
||||||
|
|
||||||
The list of instructions is presented in Figure \ref{fig:instructions} (basic arithmetic operations are partially omitted). The notation for the instructions follows the format outlined in the Lua VM documentation \cite[p.13]{ierusalimschy2005}. From left to right, the operation name, a list of operands, and the pseudo-code of the operation are displayed. When each of the three operands is used as an unsigned 8-bit integer, they are represented as \texttt{A B C}. If an operand is used as a signed integer, it is prefixed with \texttt{s}. When two operand fields are combined into a 16-bit value, the suffix \texttt{x} is added. For example, when \texttt{B} and \texttt{C} are merged and treated as a signed 16-bit value, they are represented as \texttt{sBx}.
|
The list of instructions is presented in Figure \ref{fig:instruction} (basic arithmetic operations are partially omitted). The notation for the instructions follows the format outlined in the Lua VM documentation \cite[p.13]{ierusalimschy2005}. From left to right, the operation name, a list of operands, and the pseudo-code of the operation are displayed. When each of the three operands is used as an unsigned 8-bit integer, they are represented as \texttt{A B C}. If an operand is used as a signed integer, it is prefixed with \texttt{s}. When two operand fields are combined into a 16-bit value, the suffix \texttt{x} is added. For example, when \texttt{B} and \texttt{C} are merged and treated as a signed 16-bit value, they are represented as \texttt{sBx}.
|
||||||
|
|
||||||
In the pseudo-code, \texttt{R(A)} denotes data being moved in and out of the register (or call stack) at the base pointer + \texttt{A} for the current function. \texttt{K(A)} refers to the \texttt{A}-th entry in the static variable section of the compiled program, and \texttt{U(A)} accesses the \texttt{A}-th upvalue of the current function.
|
In the pseudo-code, \texttt{R(A)} denotes data being moved in and out of the register (or call stack) at the base pointer + \texttt{A} for the current function. \texttt{K(A)} refers to the \texttt{A}-th entry in the static variable section of the compiled program, and \texttt{U(A)} accesses the \texttt{A}-th upvalue of the current function.
|
||||||
|
|
||||||
@@ -163,13 +163,13 @@
|
|||||||
|
|
||||||
The overall structure of the virtual machine (VM), program, and instantiated closures for \lambdammm\ is depicted in Figure \ref{fig:vmstructure}. In addition to the usual call stack, the VM has a dedicated storage area (a flat array) to manage internal state data for feedback and delay.
|
The overall structure of the virtual machine (VM), program, and instantiated closures for \lambdammm\ is depicted in Figure \ref{fig:vmstructure}. In addition to the usual call stack, the VM has a dedicated storage area (a flat array) to manage internal state data for feedback and delay.
|
||||||
|
|
||||||
This storage area is accompanied by pointers indicating the positions from which internal state data are retrieved via the \texttt{GETSTATE} and \texttt{SETSTATE} instructions. These positions are shifted forward or backward using the \texttt{SHIFTSTATE} instruction. The actual data layout in the state storage memory is statically determined during compilation by analyzing function calls involving references to \texttt{self}, \texttt{delay}, and other stateful functions, including those that invoke such functions recursively. The \texttt{DELAY} operation takes two inputs: \texttt{B}, representing the input value, and \texttt{C}, representing the delay time in samples.
|
This storage area is accompanied by pointers indicating the positions from which internal state data are retrieved via the \\ \texttt{GETSTATE} and \texttt{SETSTATE} instructions. These positions are shifted forward or backward using the \texttt{SHIFTSTATE} instruction. The actual data layout in the state storage memory is statically determined during compilation by analyzing function calls involving references to \texttt{self}, \texttt{delay}, and other stateful functions, including those that invoke such functions recursively. The \texttt{DELAY} operation takes two inputs: \texttt{B}, representing the input value, and \texttt{C}, representing the delay time in samples.
|
||||||
|
|
||||||
However, for higher-order functions—functions that take another function as an argument or return one—the internal state layout of the passed function is unknown at compile time. As a result, a separate internal state storage area is allocated for each instantiated closure, distinct from the global storage area maintained by the VM instance. The VM also uses an additional stack to keep track of pointers to the state storage of instantiated closures. Each time a \texttt{CALLCLS} operation is executed, the VM pushes the pointer to the closure's state storage onto the state stack. Upon completion of the closure call, the VM pops the state pointer off the stack.
|
However, for higher-order functions—functions that take another function as an argument or return one—the internal state layout of the passed function is unknown at compile time. As a result, a separate internal state storage area is allocated for each instantiated closure, distinct from the global storage area maintained by the VM instance. The VM also uses an additional stack to keep track of pointers to the state storage of instantiated closures. Each time a \texttt{CALLCLS} operation is executed, the VM pushes the pointer to the closure's state storage onto the state stack. Upon completion of the closure call, the VM pops the state pointer off the stack.
|
||||||
|
|
||||||
Instantiated closures also maintain their own storage area for upvalues. Until a closure exits the context of its parent function (known as an "Open Closure"), its upvalues hold a negative offset that references the current execution's stack. This offset is determined at compile time and stored in the function's prototype in the program. Additionally, an upvalue may reference not only local variables but also upvalues from the parent function (a situation that arises when at least three functions are nested). Thus, the array of upvalue indices in the function prototype stores a pair of values: a tag indicating whether the value is a local stack variable or an upvalue from a parent function, and the corresponding index (either the negative stack offset or the parent function's upvalue index).
|
Instantiated closures also maintain their own storage area for upvalues. Until a closure exits the context of its parent function (known as an ``Open Closure''), its upvalues hold a negative offset that references the current execution's stack. This offset is determined at compile time and stored in the function's prototype in the program. Additionally, an upvalue may reference not only local variables but also upvalues from the parent function (a situation that arises when at least three functions are nested). Thus, the array of upvalue indices in the function prototype stores a pair of values: a tag indicating whether the value is a local stack variable or an upvalue from a parent function, and the corresponding index (either the negative stack offset or the parent function's upvalue index).
|
||||||
|
|
||||||
For example, consider a scenario where the upvalue indices in the program are specified as \texttt{[upvalue(1), local(3)]}. In this case, the instruction \texttt{GETUPVALUE 6 1} indicates that the value located at index \texttt{3} from the upvalue list (referenced by \texttt{upvalue(1)}) should be retrieved from \texttt{R(-3)} relative to the base pointer, and the result should be stored in \texttt{R(6)}.
|
For example, consider a scenario where the upvalue indices in the program are specified as \texttt{[upvalue(1), local(3)]}. In this case, the instruction \texttt{GETUPVALUE 6 1} indicates that the value located at index \texttt{3} from the upvalue list (referenced by \\ \texttt{upvalue(1)}) should be retrieved from \texttt{R(-3)} relative to the base pointer, and the result should be stored in \texttt{R(6)}.
|
||||||
|
|
||||||
When a closure escapes its original function context through the \texttt{RETURN} instruction, the inserted \texttt{CLOSE} instruction moves the active upvalues from the stack to the heap memory. These upvalues may be referenced from multiple locations, especially in cases involving nested closures. As such, a garbage collection mechanism is required to free memory once these upvalues are no longer in use.
|
When a closure escapes its original function context through the \texttt{RETURN} instruction, the inserted \texttt{CLOSE} instruction moves the active upvalues from the stack to the heap memory. These upvalues may be referenced from multiple locations, especially in cases involving nested closures. As such, a garbage collection mechanism is required to free memory once these upvalues are no longer in use.
|
||||||
|
|
||||||
@@ -276,11 +276,14 @@
|
|||||||
\end{figure}
|
\end{figure}
|
||||||
|
|
||||||
|
|
||||||
Listing \ref{lst:filterbank_good} shows an example of a higher-order function \texttt{filterbank}, which takes another function \texttt{filter}—accepting an input and a frequency as arguments—duplicates \texttt{n} instances of \texttt{filter}, and adds them together. Note that in the previous specification of mimium \cite{matsuura2021a}, the syntax for variable binding and destructive assignment was the same (\texttt{x = a}). However, in the current syntax, variable binding now uses the \texttt{let} keyword. Additionally, since the semantics follow a call-by-value paradigm, reassignment syntax is no longer used in the current implementation.
|
Listing \ref{lst:filterbank_good} shows an example of a higher-order function \\ \texttt{filterbank}, which takes another function \texttt{filter}—accepting an input and a frequency as arguments—duplicates \texttt{n} instances of \texttt{filter}, and adds them together. Note that in the previous specification of mimium \cite{matsuura2021a}, the syntax for variable binding and destructive assignment was the same (\texttt{x = a}). However, in the current syntax, variable binding now uses the \texttt{let} keyword. Additionally, since the semantics follow a call-by-value paradigm, reassignment syntax is no longer used in the current implementation.
|
||||||
|
|
||||||
The previous mimium compiler was unable to compile code that took a function with an internal state as an argument because the entire tree of internal states had to be statically determined at compile time. However, the VM in \lambdammm\ can manage this dynamically. Listing \ref{lst:bytecode_filterbank} shows the translated VM instructions for this code. Recursive calls on the first line of \texttt{filterbank}, as well as calls to functions passed as arguments or obtained through upvalues (like \texttt{filter}), are executed using the \texttt{CALLCLS} instruction rather than the \texttt{CALL} instruction. The \texttt{GETSTATE} and \texttt{SETSTATE} instructions are not used in this function, as the internal state storage is switched dynamically when the \texttt{CALLCLS} instruction is interpreted.
|
The previous mimium compiler was unable to compile code that took a function with an internal state as an argument because the entire tree of internal states had to be statically determined at compile time. However, the VM in \lambdammm\ can manage this dynamically. Listing \ref{lst:bytecode_filterbank} shows the translated VM instructions for this code. Recursive calls on the first line of \texttt{filterbank}, as well as calls to functions passed as arguments or obtained through upvalues (like \texttt{filter}), are executed using the \texttt{CALLCLS} instruction rather than the \texttt{CALL} instruction. The \texttt{GETSTATE} and \texttt{SETSTATE} instructions are not used in this function, as the internal state storage is switched dynamically when the \texttt{CALLCLS} instruction is interpreted.
|
||||||
|
|
||||||
\begin{lstlisting}[float,floatplacement=H,label=lst:filterbank_good,language=Rust,caption=\it Example code that duplicates filter parametrically using a recursive function and closure.]
|
\begin{lstlisting}[float,floatplacement=H,label=lst:filterbank_good,language=Rust,caption=\it Example code that duplicates filter parametrically using a recursive function and closure.]
|
||||||
|
fn bandpass(x,freq){
|
||||||
|
//...
|
||||||
|
}
|
||||||
fn filterbank(n,filter){
|
fn filterbank(n,filter){
|
||||||
let next = filterbank(n-1,filter)
|
let next = filterbank(n-1,filter)
|
||||||
if (n>0){
|
if (n>0){
|
||||||
@@ -377,10 +380,6 @@
|
|||||||
Even though the code contains no destructive assignments, the recursive execution of the \texttt{filterbank} function occurs only once in Listing \ref{lst:filterbank_good}, during the global environment evaluation. In contrast, in Listing \ref{lst:filterbank_bad}, the recursive function is executed and a closure is generated each time the \texttt{dsp} function runs on every sample. Since the internal state of the closure is initialized at the time of closure allocation, in the example of Listing \ref{lst:filterbank_bad}, the internal state of the closure is reset at each time step following the evaluation of \texttt{filterbank}.
|
Even though the code contains no destructive assignments, the recursive execution of the \texttt{filterbank} function occurs only once in Listing \ref{lst:filterbank_good}, during the global environment evaluation. In contrast, in Listing \ref{lst:filterbank_bad}, the recursive function is executed and a closure is generated each time the \texttt{dsp} function runs on every sample. Since the internal state of the closure is initialized at the time of closure allocation, in the example of Listing \ref{lst:filterbank_bad}, the internal state of the closure is reset at each time step following the evaluation of \texttt{filterbank}.
|
||||||
|
|
||||||
\begin{lstlisting}[float,floatplacement=H,label=lst:filterbank_bad,language=Rust,caption=\it Wrong example of the code that duplicate filter parametrically.]
|
\begin{lstlisting}[float,floatplacement=H,label=lst:filterbank_bad,language=Rust,caption=\it Wrong example of the code that duplicate filter parametrically.]
|
||||||
|
|
||||||
fn bandpass(x,freq){
|
|
||||||
//...
|
|
||||||
}
|
|
||||||
fn filterbank(n,filter){
|
fn filterbank(n,filter){
|
||||||
if (n>0){
|
if (n>0){
|
||||||
|x,freq| filter(x,freq+n*100)
|
|x,freq| filter(x,freq+n*100)
|
||||||
@@ -411,7 +410,7 @@
|
|||||||
|
|
||||||
This implies that major compiler optimization techniques, such as constant folding and function inlining, cannot be directly applied to mimium. These optimizations must be performed after the global context evaluation and before the evaluation of the \texttt{dsp} function.
|
This implies that major compiler optimization techniques, such as constant folding and function inlining, cannot be directly applied to mimium. These optimizations must be performed after the global context evaluation and before the evaluation of the \texttt{dsp} function.
|
||||||
|
|
||||||
To address this issue, it would be necessary to introduce a distinction in the type system to indicate whether a term should be used during the global context evaluation (stage 0) or during actual signal processing (stage 1). This can be achieved with Multi-Stage Computation \cite{Taha1997}. Listing \ref{lst:filterbank_multi} provides an example of the \texttt{filterbank} code using BER MetaOCaml’s syntax: \texttt{.<term>.}, which generates a program to be used in the next stage, and \texttt{\textasciitilde term}, which embeds terms evaluated in the previous stage \cite{kiselyov2014a}.
|
To address this issue, it would be necessary to introduce a distinction in the type system to indicate whether a term should be used during the global context evaluation (stage 0) or during actual signal processing (stage 1). This can be achieved with Multi-Stage Computation \cite{Taha1997}. Listing \ref{lst:filterbank_multi} provides an example of the \\ \texttt{filterbank} code using BER MetaOCaml’s syntax: \texttt{.<term>.}, which generates a program to be used in the next stage, and \texttt{\textasciitilde term}, which embeds terms evaluated in the previous stage \cite{kiselyov2014a}.
|
||||||
|
|
||||||
The \texttt{filterbank} function is evaluated in stage 0 while embedding itself with \texttt{\textasciitilde}. This multi-stage computation code retains the same semantics for both the generation of the signal processing graph and the execution of the signal processing, in contrast to Faust and Kronos.
|
The \texttt{filterbank} function is evaluated in stage 0 while embedding itself with \texttt{\textasciitilde}. This multi-stage computation code retains the same semantics for both the generation of the signal processing graph and the execution of the signal processing, in contrast to Faust and Kronos.
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
%------------------------------------------------------------------------------------------
|
%------------------------------------------------------------------------------------------
|
||||||
% ! ! ! ! ! ! ! ! ! ! ! ! user defined variables ! ! ! ! ! ! ! ! ! ! ! ! ! !
|
% ! ! ! ! ! ! ! ! ! ! ! ! user defined variables ! ! ! ! ! ! ! ! ! ! ! ! ! !
|
||||||
% Please use these commands to define title and author(s) of the paper:
|
% Please use these commands to define title and author(s) of the paper:
|
||||||
\def\papertitle{$\lambda_{mmm}$: the Intermediate Representation for Synchronous Signal Processing Language Based on Lambda Calculus}
|
\def\papertitle{Lambda-mmm: the Intermediate Representation for Synchronous Signal Processing Language Based on Lambda Calculus}
|
||||||
\def\paperauthorA{Tomoya Matsuura}
|
\def\paperauthorA{Tomoya Matsuura}
|
||||||
|
|
||||||
% we should not have page number
|
% we should not have page number
|
||||||
|
|||||||
Reference in New Issue
Block a user