Skip to content

Commit e911d76

Browse files
committed
New SignalFIRCompiler.
1 parent 97ac753 commit e911d76

File tree

7 files changed

+1172
-74
lines changed

7 files changed

+1172
-74
lines changed

build/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ option ( NONDETERMINISM_LINT "Add compiler warning for nondeterminism" off)
4242
option ( INCLUDE_WASMTIME "Compile with wasmtime support" off)
4343
option ( SELF_CONTAINED_LIBRARY "Don't search system architecture files." off)
4444
option ( LINK_LLVM_STATIC "Link LLVM statically" on )
45-
option ( INCLUDE_LLVM_STATIC_IN_ARCHIVE "Combine static LLVM .a files and libfaust.a in a single .a file" on )
45+
option ( INCLUDE_LLVM_STATIC_IN_ARCHIVE "Combine static LLVM .a files and libfaust.a in a single .a file" off )
4646

4747
if (INCLUDE_WASMTIME)
4848
set(WASMTIME_LIB /usr/local/lib/libwasmtime.a)

compiler/generator/instructions_compiler.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "sigRetiming.hh"
3838
#include "sigToGraph.hh"
3939
#include "signal2Elementary.hh"
40+
#include "signalFIRCompiler.hh"
4041
#include "signalVisitor.hh"
4142
#include "sigprint.hh"
4243
#include "sigtyperules.hh"
@@ -103,6 +104,12 @@ Tree InstructionsCompiler::prepare(Tree LS)
103104
SignalTypePrinter types(L1);
104105
std::cout << types.print();
105106
throw faustexception("Dump signal type finished...\n");
107+
} else if (gGlobal->gDumpNorm == 3) {
108+
SignalFIRCompiler fir_compiler(0, 0, L1);
109+
fir_compiler.initTables();
110+
fir_compiler.compile();
111+
fir_compiler.dumpFIR();
112+
throw faustexception("Dump FIR compiler finished...\n");
106113
}
107114

108115
// No more table privatisation

compiler/global.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,10 @@ bool global::processCmdline(int argc, const char* argv[])
14561456
gDumpNorm = 2;
14571457
i += 1;
14581458

1459+
} else if (isCmd(argv[i], "-norm3", "--normalized-form3")) {
1460+
gDumpNorm = 3;
1461+
i += 1;
1462+
14591463
} else if (isCmd(argv[i], "-cn", "--class-name") && (i + 1 < argc)) {
14601464
vector<char> rep = {'@', ' ', '(', ')', '/', '\\', '.'};
14611465
gClassName = replaceCharList(argv[i + 1], rep, '_');
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
/************************************************************************
2+
************************************************************************
3+
FAUST compiler
4+
Copyright (C) 2025 GRAME, Centre National de Creation Musicale
5+
---------------------------------------------------------------------
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU Lesser General Public License as published by
8+
the Free Software Foundation; either version 2.1 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU Lesser General Public License for more details.
15+
16+
You should have received a copy of the GNU Lesser General Public License
17+
along with this program; if not, write to the Free Software
18+
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19+
************************************************************************
20+
************************************************************************/
21+
22+
#include "signalFIRCompiler.hh"
23+
#include "compatibility.hh" // For basename, pathToContent
24+
#include "xtended.hh"
25+
26+
#include <iostream>
27+
#include <string>
28+
#include <vector>
29+
30+
using namespace std;
31+
32+
//-------------------------SignalFIRCompiler-------------------------------
33+
//
34+
// SignalFIRCompiler is designed to directly compile signals. The compilation process is divided
35+
// into two main stages:
36+
//
37+
// 1) Preparation Stage (SignalBuilder). The SignalBuilder class traverses all output signal trees
38+
// to:
39+
// - Allocate delay lines (both integer and real types) for sample-accurate delays and
40+
// recursive constructs.
41+
// - Allocate tables (both integer and real types) required for table-based signal generation.
42+
// - Collect and configure input and output control signals (e.g., sliders, buttons,
43+
// bargraphs).
44+
//
45+
// 2) Compilation Stage (SignalFIRCompiler). The SignalFIRCompiler class:
46+
// - Traverses all output signal trees.
47+
// - Compile the value of each output signal sample by recursively compiling the expression
48+
// tree.
49+
// - Uses a value stack to manage intermediate compilaiton results.
50+
//
51+
// After SignalBuilder has prepared the signal trees, the tables are precompiled once during
52+
// the initialization phase via the `initTables` method.
53+
//
54+
// `compile()` iterates over every output in the linked list `fOutputSig`.
55+
// * For each signal it:
56+
// * 1. Recursively traverses the expression tree with `self()`.
57+
// * 2. Retrieves the resulting FIR value from `fValueStack`.
58+
// * 3. Stores that value in a fresh stack variable via `StoreStackVar`.
59+
//
60+
//----------------------------------------------------------------------
61+
62+
void SignalFIRCompiler::compile()
63+
{
64+
Tree output_list = fOutputSig;
65+
fVisited.clear(); // Clear visited for each top-level signal evaluation per sample
66+
67+
while (!isNil(output_list)) {
68+
// Compile each output
69+
Tree out_sig = hd(output_list);
70+
self(out_sig);
71+
// Get compiled value and sotre in the output
72+
ValueInst* res = popRes();
73+
writeStatement(out_sig, IB::genStoreStackVar(
74+
gGlobal->getFreshID("output"),
75+
genCastedOutput(getCertifiedSigType(out_sig)->nature(), res)));
76+
// Compile next output
77+
output_list = tl(output_list);
78+
}
79+
}
80+
81+
/**
82+
* @brief Visits a signal tree node and recursively compiles its value.
83+
*
84+
* This method implements the core compiler logic for the
85+
* signal graph. It uses a recursive traversal to process each node type,
86+
* compiles its sub-expressions, and produce the resulting FIR value. The
87+
* intermediate results are stored on a value stack (`fValueStack`).
88+
*
89+
* The method supports a wide variety of Faust signal constructs, including:
90+
* - Constants (integer, real)
91+
* - Inputs and outputs
92+
* - Delay lines and feedback structures
93+
* - Control structures (sliders, buttons, bargraphs)
94+
* - Mathematical operations (binary operators, conditional expressions)
95+
* - Table-based operations (read/write table)
96+
* - Recursive signals and projections
97+
*
98+
* Key implementation notes:
99+
* - For each recognized node type, it performs the appropriate compilation logic
100+
* and pushes the result onto the value stack.
101+
* - For recursive signals (e.g., projections), it uses the `fVisited` map to
102+
* detect cycles and avoid infinite recursion.
103+
* - It handles the compilation of user interface controls by compilation loding values
104+
* from `fInputControls` and updating `fOutputControls`.
105+
* - For unimplemented or unrecognized nodes, it triggers an assertion failure
106+
* to ensure correctness.
107+
*
108+
* @param sig The signal tree node to compile.
109+
*/
110+
void SignalFIRCompiler::visit(Tree sig)
111+
{
112+
int i_val;
113+
int64_t i64_val;
114+
double r_val;
115+
Tree size_tree, gen_tree, wi_tree, ws_tree, tbl_tree, ri_tree;
116+
Tree c_tree, x_tree, y_tree, z_tree;
117+
Tree label_tree, type_tree, name_tree, file_tree, sf_tree, sel;
118+
Tree rec_vars, rec_exprs;
119+
int opt_op;
120+
int proj_idx;
121+
122+
/*
123+
if (global::isDebug("SIG_RENDERER")) {
124+
std::cout << "SignalFIRCompiler : " << ppsig(sig, 64) << std::endl;
125+
std::cout << "SignalFIRCompiler : fIOTA " << fIOTA << std::endl;
126+
}
127+
*/
128+
129+
if (xtended* xt = (xtended*)getUserData(sig)) {
130+
list<ValueInst*> args;
131+
vector<Typed::VarType> atypes;
132+
// Compile all arguments then compile the function call
133+
for (Tree b : sig->branches()) {
134+
self(b);
135+
args.push_back(popRes());
136+
atypes.push_back(convert2FIRType(getCertifiedSigType(b)->nature()));
137+
}
138+
139+
// ValueInst* res = xt->compute(args);
140+
141+
// HACK: for 'min/max' res may actually be of type kInt
142+
int rtype = getCertifiedSigType(sig)->nature();
143+
144+
// Compile the function declaration
145+
fGlobalBlock->pushBackInst(IB::genFunction(xt->name(), convert2FIRType(rtype), atypes));
146+
// pushRes((rtype == kInt) ? Node(int(res.getDouble())) : res);
147+
// Compile the function call
148+
pushRes(IB::genFunCallInst(xt->name(), args));
149+
150+
} else if (isSigInt(sig, &i_val)) {
151+
pushRes(IB::genInt32NumInst(i_val));
152+
} else if (isSigInt64(sig, &i64_val)) {
153+
pushRes(IB::genInt32NumInst(i64_val));
154+
} else if (isSigReal(sig, &r_val)) {
155+
pushRes(IB::genRealNumInst(itfloat(), r_val));
156+
} else if (isSigInput(sig, &i_val)) {
157+
// IB::genLoadVar("in" + std::to_string(i_val), Address::kFunArgs);
158+
// pushRes(fInputs[i_val][fSample]);
159+
pushRes(IB::genLoadStackVar(gGlobal->getFreshID("input")));
160+
} else if (isSigOutput(sig, &i_val, x_tree)) {
161+
self(x_tree); // Evaluate the expression connected to the output
162+
} else if (isSigDelay1(sig, x_tree)) {
163+
self(x_tree);
164+
ValueInst* v1 = popRes();
165+
ValueInst* one = IB::genInt32NumInst(1);
166+
pushRes(writeReadDelay(x_tree, v1, one));
167+
168+
} else if (isSigDelay(sig, x_tree, y_tree)) {
169+
if (isZeroDelay(y_tree)) {
170+
self(x_tree);
171+
} else {
172+
self(x_tree);
173+
ValueInst* v1 = popRes();
174+
self(y_tree);
175+
ValueInst* v2 = popRes();
176+
pushRes(writeReadDelay(x_tree, v1, v2));
177+
}
178+
} else if (isSigSelect2(sig, sel, x_tree, y_tree)) {
179+
// Interpret the condition and both branches
180+
self(sel);
181+
ValueInst* sel_val = popRes();
182+
self(x_tree);
183+
ValueInst* x_val = popRes();
184+
self(y_tree);
185+
ValueInst* y_val = popRes();
186+
// Inverted
187+
pushRes(IB::genSelect2Inst(sel_val, y_val, x_val));
188+
} else if (isSigPrefix(sig, x_tree, y_tree)) {
189+
/*
190+
// TODO
191+
self(y_tree);
192+
if (fIOTA == 0) {
193+
self(x_tree);
194+
}
195+
*/
196+
self(y_tree);
197+
} else if (isSigBinOp(sig, &opt_op, x_tree, y_tree)) {
198+
self(x_tree);
199+
ValueInst* v1 = popRes();
200+
self(y_tree);
201+
ValueInst* v2 = popRes();
202+
if ((opt_op == kMul) && isMinusOne(x_tree)) {
203+
pushRes(IB::genMinusInst(v2));
204+
} else if ((opt_op == kMul) && isMinusOne(y_tree)) {
205+
pushRes(IB::genMinusInst(v1));
206+
} else {
207+
pushRes(IB::genBinopInst(opt_op, v1, v2));
208+
}
209+
} else if (isSigFConst(sig, type_tree, name_tree, file_tree)) {
210+
// Special case for SR constant
211+
if (string(tree2str(name_tree)) == "fSamplingFreq") {
212+
pushRes(IB::genLoadStructVar("fSamplerate"));
213+
} else {
214+
// TODO
215+
faustassert(false);
216+
pushRes(IB::genTypedZero(itfloat()));
217+
}
218+
} else if (isSigWRTbl(sig, size_tree, gen_tree, wi_tree, ws_tree)) {
219+
if (isNil(wi_tree)) {
220+
// Nothing
221+
} else {
222+
self(wi_tree);
223+
ValueInst* write_idx = popRes();
224+
self(ws_tree);
225+
ValueInst* val = popRes();
226+
writeTable(sig, write_idx, val);
227+
}
228+
} else if (isSigRDTbl(sig, tbl_tree, ri_tree)) {
229+
// Compiles table
230+
self(tbl_tree);
231+
// Then compile the access
232+
self(ri_tree);
233+
ValueInst* read_idx = popRes();
234+
pushRes(readTable(tbl_tree, read_idx));
235+
236+
} else if (isSigGen(sig, x_tree)) {
237+
if (fVisitGen) {
238+
self(x_tree);
239+
} else {
240+
pushRes(IB::genTypedZero(itfloat()));
241+
}
242+
} else if (isSigWaveform(sig)) {
243+
// Modulo based access in the waveform
244+
// int size = sig->arity();
245+
// int index = fIOTA % size;
246+
// self(sig->branch(index));
247+
// TODO
248+
self(sig->branch(0));
249+
250+
} else if (isProj(sig, &proj_idx, x_tree) && isRec(x_tree, rec_vars, rec_exprs)) {
251+
// First visit of the recursive signal
252+
if (fVisited.find(sig) == fVisited.end()) {
253+
faustassert(isRec(x_tree, rec_vars, rec_exprs));
254+
fVisited[sig]++;
255+
// Render the actual projection
256+
self(nth(rec_exprs, proj_idx));
257+
ValueInst* res = popRes();
258+
/*
259+
if (global::isDebug("SIG_RENDERER")) {
260+
std::cout << "Proj : " << res << "\n";
261+
}
262+
*/
263+
ValueInst* zero = IB::genInt32NumInst(0);
264+
pushRes(writeReadDelay(sig, res, zero));
265+
266+
} else {
267+
/*
268+
if (global::isDebug("SIG_RENDERER")) {
269+
std::cout << "SignalFIRCompiler : next visit of the recursive signal\n";
270+
}
271+
*/
272+
ValueInst* zero = IB::genInt32NumInst(0);
273+
pushRes(readDelay(sig, zero));
274+
}
275+
} else if (isSigIntCast(sig, x_tree)) {
276+
self(x_tree);
277+
ValueInst* cur = popRes();
278+
pushRes(IB::genCastInt32Inst(cur));
279+
} else if (isSigBitCast(sig, x_tree)) {
280+
// Bitcast is complex. For a simple renderer, it might be an identity if types are
281+
// "close enough" or a reinterpretation of bits (e.g., float bits as int). This renderer
282+
// doesn't have type info readily on Node to do a true bitcast. Assuming it's a numeric
283+
// pass-through for now.
284+
self(x_tree);
285+
} else if (isSigFloatCast(sig, x_tree)) {
286+
self(x_tree);
287+
ValueInst* cur = popRes();
288+
pushRes(IB::genCastInst(cur, IB::genBasicTyped(itfloat())));
289+
} else if (isSigButton(sig, label_tree)) {
290+
pushRes(fInputControls[sig].getValue());
291+
} else if (isSigCheckbox(sig, label_tree)) {
292+
pushRes(fInputControls[sig].getValue());
293+
} else if (isSigVSlider(sig, label_tree, c_tree, x_tree, y_tree, z_tree)) {
294+
pushRes(fInputControls[sig].getValue());
295+
} else if (isSigHSlider(sig, label_tree, c_tree, x_tree, y_tree, z_tree)) {
296+
pushRes(fInputControls[sig].getValue());
297+
} else if (isSigNumEntry(sig, label_tree, c_tree, x_tree, y_tree, z_tree)) {
298+
pushRes(fInputControls[sig].getValue());
299+
} else if (isSigVBargraph(sig, label_tree, x_tree, y_tree, z_tree)) {
300+
self(z_tree);
301+
ValueInst* val = topRes();
302+
writeStatement(z_tree, fOutputControls[sig].setValue(val));
303+
} else if (isSigHBargraph(sig, label_tree, x_tree, y_tree, z_tree)) {
304+
self(z_tree);
305+
ValueInst* val = topRes();
306+
writeStatement(z_tree, fOutputControls[sig].setValue(val));
307+
} else if (isSigSoundfile(sig, label_tree)) {
308+
// TODO: Implement soundfile reading. Requires state management for file handlers,
309+
// position, etc.
310+
pushRes(IB::genTypedZero(itfloat()));
311+
} else if (isSigSoundfileLength(sig, sf_tree, x_tree)) {
312+
// TODO
313+
self(sf_tree);
314+
popRes();
315+
self(x_tree);
316+
popRes();
317+
pushRes(IB::genTypedZero(itfloat()));
318+
} else if (isSigSoundfileRate(sig, sf_tree, x_tree)) {
319+
// TODO
320+
self(sf_tree);
321+
popRes();
322+
self(x_tree);
323+
popRes();
324+
pushRes(IB::genTypedZero(itfloat()));
325+
} else if (isSigSoundfileBuffer(sig, sf_tree, x_tree, y_tree, z_tree)) {
326+
// TODO
327+
self(sf_tree);
328+
popRes();
329+
self(x_tree);
330+
popRes();
331+
self(y_tree);
332+
popRes();
333+
self(z_tree);
334+
popRes();
335+
pushRes(IB::genTypedZero(itfloat()));
336+
} else if (isSigAttach(sig, x_tree, y_tree)) {
337+
// Interpret second arg then drop it
338+
self(y_tree);
339+
popRes();
340+
// And return the first one
341+
self(x_tree);
342+
} else if (isSigEnable(sig, x_tree, y_tree)) { // x_tree is condition, y_tree is signal
343+
self(x_tree);
344+
Node enable = popRes();
345+
if (enable.getInt() != 0) {
346+
self(y_tree);
347+
} else {
348+
pushRes(IB::genTypedZero(itfloat()));
349+
}
350+
} else if (isSigControl(sig, x_tree, y_tree)) { // x_tree is name, y_tree is signal
351+
self(y_tree);
352+
} else {
353+
// Default case and recursion
354+
SignalVisitor::visit(sig);
355+
}
356+
}
357+
358+
// External API

0 commit comments

Comments
 (0)