add citeproc_code

This commit is contained in:
2024-09-19 19:01:32 +09:00
parent 975af1851c
commit 36d677fe4b
4 changed files with 963 additions and 9 deletions

View File

@@ -221,7 +221,7 @@ This storage area is accompanied by data indicating the position from which the
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 given 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 instance itself. The VM switches the `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 ongoing execution. Because this offset value can be determined at compile time, stored in the function prototype in the program. For instance, if the Upvalue indexes in the program were `[4,3]`, `GETUPVALUE 6 1` means that, take `3` from the upvalue indexes and get value from `R(-2)` (3-5) over the base pointer and store it to `R(6)`.
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 ongoing execution. Because this offset value can be determined at compile time, stored in the function prototype in the program. For instance, if the Upvalue indexes in the program were `[4,3]`, `GETUPVALUE 6 1` means that, take `3` from the upvalue indexes and get value from `R(-2)` (3-5) over the base pointer and store it to `R(6)`.
When the closure escapes from the original function with\
`RETURN` instruction, inserted `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.
@@ -247,11 +247,11 @@ CONSTANTS:[1.0]
RETURN 3 1
```
Listing [\[lst:bytecodes_onepole\]](#lst:bytecodes_onepole){reference-type="ref" reference="lst:bytecodes_onepole"} shows an basic example when the *mimium* code in Listing [\[lst:onepole\]](#lst:onepole){reference-type="ref" reference="lst:onepole"} is compiled into VM bytecode. When `self` is referenced, the value is obtained with the `GETSTATE` instruction, and the internal state is updated by storing the return value with the `SETSTATE` instruction before returning the value with `RETURN` from the function. Here, the actual return value is obtained by the second `GETSTATE` instruction in order to return the initial value of the internal state when time=0.
Listing [\[lst:bytecodes_onepole\]](#lst:bytecodes_onepole) shows an basic example when the *mimium* code in Listing [\[lst:onepole\]](#lst:onepole) is compiled into VM bytecode. When `self` is referenced, the value is obtained with the `GETSTATE` instruction, and the internal state is updated by storing the return value with the `SETSTATE` instruction before returning the value with `RETURN` from the function. Here, the actual return value is obtained by the second `GETSTATE` instruction in order to return the initial value of the internal state when time=0.
For example, when a time counter is written as `| | {self + 1}`, it is the compiler's design choice whether the return value of time=0 should be 0 or 1 though the latter does not strictly follow the semantics E-FEED in Figure [\[fig:semantics\]](#fig:semantics){reference-type="ref" reference="fig:semantics"}. If the design is to return 1 when time = 0, the second `GETSTATE` instruction can be removed and the value for the `RETURN` instruction should be `R(2)`.
For example, when a time counter is written as `| | {self + 1}`, it is the compiler's design choice whether the return value of time=0 should be 0 or 1 though the latter does not strictly follow the semantics E-FEED in Figure [\[fig:semantics\]](#fig:semantics). If the design is to return 1 when time = 0, the second `GETSTATE` instruction can be removed and the value for the `RETURN` instruction should be `R(2)`.
A more complex example code and its expected bytecode instructions are shown in Listing [\[lst:fbdelay\]](#lst:fbdelay){reference-type="ref" reference="lst:fbdelay"} and Listing [\[lst:bytecodes_fbdelay\]](#lst:bytecodes_fbdelay){reference-type="ref" reference="lst:bytecodes_fbdelay"}. The codes define delay with a feedback as `fbdelay`, the other function `twodelay` uses two feedback delay with different parameters, and `dsp` finally uses two `twodelay` function.
A more complex example code and its expected bytecode instructions are shown in Listing [\[lst:fbdelay\]](#lst:fbdelay) and Listing [\[lst:bytecodes_fbdelay\]](#lst:bytecodes_fbdelay). The codes define delay with a feedback as `fbdelay`, the other function `twodelay` uses two feedback delay with different parameters, and `dsp` finally uses two `twodelay` function.
Each after the referring to `self` through `GETSTATE` instruction, or call to the other statefull function,\
`SHIFTSTATE` instruction inserted to move the `StatePtr` forward to prepare the next non-closure function call. Before exits function, `StatePtr` is reset to the same position as that the current function context has begun by `SHIFTSTATE` (A sum of the operand for `SHIFTSTATE` in a function must be always 0).
@@ -321,11 +321,11 @@ RETURN 1 1
Because *mimium* treats functions that have internal states which change over time, when higher-order functions are used, there is a counterintuitive behavior compared to general functional programming languages.
An example is the higher-order function `filterbank`, which duplicates `filter` function `N` times parametrically, and adds them together. Listing [\[lst:filterbank_bad\]](#lst:filterbank_bad) is an example of an incorrect code, and Listing [\[lst:filterbank_good\]](#lst:filterbank_good) is an example of the code that behave correctly. The difference between Listing [\[lst:filterbank_bad\]](#lst:filterbank_bad){reference-type="ref" reference="lst:filterbank_bad"} and Listing [\[lst:filterbank_good\]](#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 `let` binding out of the inner function[^2]. Similarly, in the `dsp` function that will be called by the audio driver in *mimium*, the difference is whether the filterbank function is executed inside `dsp` or bound with `let` once in the global context.
An example is the higher-order function `filterbank`, which duplicates `filter` function `N` times parametrically, and adds them together. Listing [\[lst:filterbank_bad\]](#lst:filterbank_bad) is an example of an incorrect code, and Listing [\[lst:filterbank_good\]](#lst:filterbank_good) is an example of the code that behave correctly. The difference between Listing [\[lst:filterbank_bad\]](#lst:filterbank_bad) and Listing [\[lst:filterbank_good\]](#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 `let` binding out of the inner function[^2]. Similarly, in the `dsp` function that will be called by the audio driver in *mimium*, the difference is whether the filterbank function is executed inside `dsp` or bound with `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 [\[lst:filterbank_good\]](#lst:filterbank_good) to Listing [\[lst:filterbank_bad\]](#lst:filterbank_bad){reference-type="ref" reference="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 `let` is manually replaced with its term (beta reduction), as in the conversion from Listing [\[lst:filterbank_good\]](#lst:filterbank_good) to Listing [\[lst:filterbank_bad\]](#lst:filterbank_bad).
But in *mimium*, there are two major stages of evaluation, 0: the code is evaluated in the global environment (concretizing the signal processing graph) at first, and 1: 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 `filterbank` function is performed only once in Listing [\[lst:filterbank_good\]](#lst:filterbank_good){reference-type="ref" reference="lst:filterbank_good"} for the evaluation of the global environment, whereas in Listing [\[lst:filterbank_bad\]](#lst:filterbank_bad){reference-type="ref" reference="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[\[lst:filterbank_bad\]](#lst:filterbank_bad){reference-type="ref" reference="lst:filterbank_bad"}, the internal state of the closure after the evaluation of `filterbank` is reset at each time step.
But in *mimium*, there are two major stages of evaluation, 0: the code is evaluated in the global environment (concretizing the signal processing graph) at first, and 1: 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 `filterbank` function is performed only once in Listing [\[lst:filterbank_good\]](#lst:filterbank_good) for the evaluation of the global environment, whereas in Listing [\[lst:filterbank_bad\]](#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[\[lst:filterbank_bad\]](#lst:filterbank_bad), the internal state of the closure after the evaluation of `filterbank` is reset at each time step.
```rust {#lst:filterbank_bad .Rust float="" floatplacement="H" label="lst:filterbank_bad" language="Rust" caption="\\it Wrong example of the code that duplicate filter parametrically."}
fn bandpass(x,freq){
@@ -376,7 +376,7 @@ fn filterbank(n,filter){
This means that the major compiler optimization techniques such as the constant folding and the function inlining can not simply be appropriated. Those optimizations should be done after the evaluation of a global context and before evaluating `dsp` function.
To solve this situation, introducing distinction whether the term should be used in global context evaluation(Stage 0) and in the actual signal processing(stage 1) in type system. This can be realized with Multi-Stage Computation[@Taha1997]. Listing [\[lst:filterbank_multi\]](#lst:filterbank_multi){reference-type="ref" reference="lst:filterbank_multi"} is the example of `filterbank` code using BER MetaOCaml's syntaxes `.<term>.` which will generate evaluated program used in a next stage, and `~term` which embed terms evaluated at the previous stage[@kiselyov2014a].
To solve this situation, introducing distinction whether the term should be used in global context evaluation(Stage 0) and in the actual signal processing(stage 1) in type system. This can be realized with Multi-Stage Computation[@Taha1997]. Listing [\[lst:filterbank_multi\]](#lst:filterbank_multi) is the example of `filterbank` code using BER MetaOCaml's syntaxes `.<term>.` which will generate evaluated program used in a next stage, and `~term` which embed terms evaluated at the previous stage[@kiselyov2014a].
`filterbank` function is evaluated in stage 0 while embedding itself by using `~`.
@@ -384,7 +384,7 @@ This multi-stage computation code has a same semantics in a generative signal gr
# Conclusion
[\[sec:conclusion\]](#sec:conclusion){reference-type="ref" reference="sec:conclusion"}
[\[sec:conclusion\]](#sec:conclusion)
This paper proposed $\lambda_{mmm}$, an intermediate representation for the programming languages for music and signal processing with the virtual machine and instruction set to run it. $\lambda_{mmm}$enables to describe generative signal graph and its contents in a single syntax and semantics. However, user have to be responsible to write codes that does not create escapable closures during the dsp execution and this problem would be difficult to understand by novice users.
@@ -396,3 +396,6 @@ JP19K21615). Also great thanks for many anonymous reviewers.
[^1]: Reason for this is that it is easy to implemented on `enum` data structure on Rust, a host language of the latest *mimium* compiler. Operands bitwidth and alignment may be changed in the future.
[^2]: In the previous specification of *mimium* in [@matsuura2021a], the binding of new variable and destructive assignment were the same syntax(`x = a`) but the syntax for the variable binding has changed to use `let` keyword.
::: {#refs}
:::