1 /************************************************************************
2  ************************************************************************
3     FAUST compiler
4     Copyright (C) 2003-2018 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 General Public License as published by
8     the Free Software Foundation; either version 2 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 General Public License for more details.
15 
16     You should have received a copy of the GNU 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 "wast_code_container.hh"
23 #include "Text.hh"
24 #include "exception.hh"
25 #include "floats.hh"
26 #include "global.hh"
27 
28 using namespace std;
29 
30 /*
31  WAST backend and module description:
32 
33  - mathematical functions are either part of WebAssembly (like f32.sqrt, f32.main, f32.max), are imported from JS
34  "global.Math", or are externally implemented (fmod and remainder in JS)
35  - local variables have to be declared first on the block, before being actually initialized or set: this is done using
36  MoveVariablesInFront3
37  - 'faustpower' function fallbacks to regular 'pow' (see powprim.h)
38  - subcontainers are inlined in 'classInit' and 'instanceConstants' functions
39  - waveform generation is 'inlined' using MoveVariablesInFront3, done in a special version of generateInstanceInitFun
40  - integer 'min/max' is done in the module in 'min_i/max_i' (using lt/select)
41  - memory can be allocated internally in the module and exported, or externally in JS and imported
42  - the JSON string is written at offset 0 in a data segment. This string *has* to be converted in a JS string *before*
43  using the DSP instance
44  - memory module size cannot be written while generating the output stream, since DSP size is computed when inlining
45  subcontainers and waveform. The final memory size is finally written after module code generation.
46  - in Load/Store, check if address is constant, so that to be used as an 'offset'
47  - move loop 'i' variable by bytes instead of frames to save index code generation of input/output accesses
48  (gLoopVarInBytes)
49  - offset of inputs/outputs are constant, so can be directly generated
50 
51 */
52 
produceFactory()53 dsp_factory_base* WASTCodeContainer::produceFactory()
54 {
55     return new text_dsp_factory_aux(
56         fKlassName, "", "",
57         ((dynamic_cast<ostringstream*>(fOut)) ? dynamic_cast<ostringstream*>(fOut)->str() : ""), fHelper.str());
58 }
59 
WASTCodeContainer(const string & name,int numInputs,int numOutputs,std::ostream * out,bool internal_memory)60 WASTCodeContainer::WASTCodeContainer(const string& name, int numInputs, int numOutputs, std::ostream* out,
61                                      bool internal_memory)
62     : fOut(out)
63 {
64     initialize(numInputs, numOutputs);
65     fKlassName      = name;
66     fInternalMemory = internal_memory;
67 
68     // Allocate one static visitor to be shared by main and sub containers
69     if (!gGlobal->gWASTVisitor) {
70         gGlobal->gWASTVisitor = new WASTInstVisitor(&fOutAux, fInternalMemory);
71     }
72 }
73 
createScalarContainer(const string & name,int sub_container_type)74 CodeContainer* WASTCodeContainer::createScalarContainer(const string& name, int sub_container_type)
75 {
76     return new WASTScalarCodeContainer(name, 0, 1, &fOutAux, sub_container_type, true);
77 }
78 
createScalarContainer(const string & name,int sub_container_type,bool internal_memory)79 CodeContainer* WASTCodeContainer::createScalarContainer(const string& name, int sub_container_type,
80                                                         bool internal_memory)
81 {
82     return new WASTScalarCodeContainer(name, 0, 1, &fOutAux, sub_container_type, internal_memory);
83 }
84 
createContainer(const string & name,int numInputs,int numOutputs,ostream * dst,bool internal_memory)85 CodeContainer* WASTCodeContainer::createContainer(const string& name, int numInputs, int numOutputs, ostream* dst,
86                                                   bool internal_memory)
87 {
88     CodeContainer* container;
89 
90     if (gGlobal->gFloatSize == 3) {
91         throw faustexception("ERROR : quad format not supported for WebAssembly\n");
92     }
93     if (gGlobal->gOpenCLSwitch) {
94         throw faustexception("ERROR : OpenCL not supported for WebAssembly\n");
95     }
96     if (gGlobal->gCUDASwitch) {
97         throw faustexception("ERROR : CUDA not supported for WebAssembly\n");
98     }
99 
100     if (gGlobal->gOpenMPSwitch) {
101         throw faustexception("ERROR : OpenMP not supported for WebAssembly\n");
102     } else if (gGlobal->gSchedulerSwitch) {
103         throw faustexception("ERROR : Scheduler mode not supported for WebAssembly\n");
104     } else if (gGlobal->gVectorSwitch) {
105         // throw faustexception("ERROR : Vector mode not supported for WebAssembly\n");
106         if (gGlobal->gVectorLoopVariant == 0) {
107             throw faustexception("ERROR : Vector mode with -lv 0 not supported for WebAssembly\n");
108         }
109         container = new WASTVectorCodeContainer(name, numInputs, numOutputs, dst, internal_memory);
110     } else {
111         container = new WASTScalarCodeContainer(name, numInputs, numOutputs, dst, kInt, internal_memory);
112     }
113 
114     return container;
115 }
116 
117 // Scalar
WASTScalarCodeContainer(const string & name,int numInputs,int numOutputs,std::ostream * out,int sub_container_type,bool internal_memory)118 WASTScalarCodeContainer::WASTScalarCodeContainer(const string& name, int numInputs, int numOutputs, std::ostream* out,
119                                                  int sub_container_type, bool internal_memory)
120     : WASTCodeContainer(name, numInputs, numOutputs, out, internal_memory)
121 {
122     fSubContainerType = sub_container_type;
123 }
124 
125 // Special version that uses MoveVariablesInFront3 to inline waveforms...
generateInstanceInitFun(const string & name,const string & obj,bool ismethod,bool isvirtual)126 DeclareFunInst* WASTCodeContainer::generateInstanceInitFun(const string& name, const string& obj, bool ismethod,
127                                                            bool isvirtual)
128 {
129     list<NamedTyped*> args;
130     if (!ismethod) {
131         args.push_back(InstBuilder::genNamedTyped(obj, Typed::kObj_ptr));
132     }
133     args.push_back(InstBuilder::genNamedTyped("sample_rate", Typed::kInt32));
134 
135     BlockInst* init_block = InstBuilder::genBlockInst();
136     init_block->pushBackInst(MoveVariablesInFront3().getCode(fStaticInitInstructions));
137     init_block->pushBackInst(MoveVariablesInFront3().getCode(fInitInstructions));
138     init_block->pushBackInst(MoveVariablesInFront3().getCode(fPostInitInstructions));
139     init_block->pushBackInst(MoveVariablesInFront3().getCode(fResetUserInterfaceInstructions));
140     init_block->pushBackInst(MoveVariablesInFront3().getCode(fClearInstructions));
141 
142     init_block->pushBackInst(InstBuilder::genRetInst());
143 
144     // Creates function
145     return InstBuilder::genVoidFunction(name, args, init_block, isvirtual);
146 }
147 
produceClass()148 void WASTCodeContainer::produceClass()
149 {
150     int n = 0;
151     gGlobal->gWASTVisitor->Tab(n);
152 
153     tab(n, fOutAux);
154     fOutAux << "(module";
155 
156     // Global declarations (mathematical functions, global variables...)
157     gGlobal->gWASTVisitor->Tab(n + 1);
158 
159     // Sub containers are merged in the main module, before functions generation
160     mergeSubContainers();
161 
162     // All mathematical functions (got from math library as variables) have to be first
163     generateGlobalDeclarations(gGlobal->gWASTVisitor);
164     generateExtGlobalDeclarations(gGlobal->gWASTVisitor);
165 
166     // Exported functions
167     tab(n + 1, fOutAux);
168     fOutAux << "(export \"getNumInputs\" (func $getNumInputs))";
169     tab(n + 1, fOutAux);
170     fOutAux << "(export \"getNumOutputs\" (func $getNumOutputs))";
171     tab(n + 1, fOutAux);
172     fOutAux << "(export \"getSampleRate\" (func $getSampleRate))";
173     tab(n + 1, fOutAux);
174     fOutAux << "(export \"init\" (func $init))";
175     tab(n + 1, fOutAux);
176     fOutAux << "(export \"instanceInit\" (func $instanceInit))";
177     tab(n + 1, fOutAux);
178     fOutAux << "(export \"instanceConstants\" (func $instanceConstants))";
179     tab(n + 1, fOutAux);
180     fOutAux << "(export \"instanceResetUserInterface\" (func $instanceResetUserInterface))";
181     tab(n + 1, fOutAux);
182     fOutAux << "(export \"instanceClear\" (func $instanceClear))";
183     tab(n + 1, fOutAux);
184     fOutAux << "(export \"setParamValue\" (func $setParamValue))";
185     tab(n + 1, fOutAux);
186     fOutAux << "(export \"getParamValue\" (func $getParamValue))";
187     tab(n + 1, fOutAux);
188     fOutAux << "(export \"compute\" (func $compute))";
189 
190     // General imports
191     tab(n + 1, fOutAux);
192     fOutAux << "(import \"env\" \"memoryBase\" (global $memoryBase i32))";
193     tab(n + 1, fOutAux);
194     fOutAux << "(import \"env\" \"tableBase\" (global $tableBase i32))";
195 
196     // Fields : compute the structure size to use in 'new'
197     gGlobal->gWASTVisitor->Tab(n + 1);
198     generateDeclarations(gGlobal->gWASTVisitor);
199 
200     // Keep location of memory generation
201     streampos begin_memory = fOutAux.tellp();
202 
203     // Always generated mathematical functions
204     tab(n + 1, fOutAux);
205     WASInst::generateIntMin()->accept(gGlobal->gWASTVisitor);
206     WASInst::generateIntMax()->accept(gGlobal->gWASTVisitor);
207 
208     // getNumInputs/getNumOutputs
209     generateGetInputs("getNumInputs", "dsp", false, false)->accept(gGlobal->gWASTVisitor);
210     generateGetOutputs("getNumOutputs", "dsp", false, false)->accept(gGlobal->gWASTVisitor);
211 
212     // Inits
213     tab(n + 1, fOutAux);
214     fOutAux << "(func $classInit (param $dsp i32) (param $sample_rate i32)";
215     tab(n + 2, fOutAux);
216     gGlobal->gWASTVisitor->Tab(n + 2);
217     {
218         BlockInst* inlined = inlineSubcontainersFunCalls(fStaticInitInstructions);
219         generateWASTBlock(inlined);
220     }
221     back(1, fOutAux);
222     fOutAux << ")";
223 
224     tab(n + 1, fOutAux);
225     fOutAux << "(func $instanceConstants (param $dsp i32) (param $sample_rate i32)";
226     tab(n + 2, fOutAux);
227     gGlobal->gWASTVisitor->Tab(n + 2);
228     {
229         BlockInst* inlined = inlineSubcontainersFunCalls(fInitInstructions);
230         generateWASTBlock(inlined);
231     }
232     back(1, fOutAux);
233     fOutAux << ")";
234 
235     tab(n + 1, fOutAux);
236     fOutAux << "(func $instanceResetUserInterface (param $dsp i32)";
237     tab(n + 2, fOutAux);
238     gGlobal->gWASTVisitor->Tab(n + 2);
239     {
240         // Rename 'sig' in 'dsp' and remove 'dsp' allocation
241         generateWASTBlock(DspRenamer().getCode(fResetUserInterfaceInstructions));
242     }
243     back(1, fOutAux);
244     fOutAux << ")";
245 
246     tab(n + 1, fOutAux);
247     fOutAux << "(func $instanceClear (param $dsp i32)";
248     tab(n + 2, fOutAux);
249     gGlobal->gWASTVisitor->Tab(n + 2);
250     {
251         // Rename 'sig' in 'dsp' and remove 'dsp' allocation
252         generateWASTBlock(DspRenamer().getCode(fClearInstructions));
253     }
254     back(1, fOutAux);
255     fOutAux << ")";
256 
257     gGlobal->gWASTVisitor->Tab(n + 1);
258 
259     // TO REMOVE when 'soundfile' is implemented
260     {
261         // Generate UI: only to trigger exception when using 'soundfile' primitive
262         generateUserInterface(gGlobal->gWASTVisitor);
263     }
264 
265     // init
266     generateInit("init", "dsp", false, false)->accept(gGlobal->gWASTVisitor);
267 
268     // instanceInit
269     generateInstanceInit("instanceInit", "dsp", false, false)->accept(gGlobal->gWASTVisitor);
270 
271     // getSampleRate
272     generateGetSampleRate("getSampleRate", "dsp", false, false)->accept(gGlobal->gWASTVisitor);
273 
274     // setParamValue
275     tab(n + 1, fOutAux);
276     fOutAux << "(func $setParamValue (param $dsp i32) (param $index i32) (param $value " << realStr << ")";
277     tab(n + 2, fOutAux);
278     fOutAux << "(" << realStr << ".store ";
279     tab(n + 3, fOutAux);
280     fOutAux << "(i32.add (local.get $dsp) (local.get $index))";
281     tab(n + 3, fOutAux);
282     fOutAux << "(local.get $value)";
283     tab(n + 2, fOutAux);
284     fOutAux << ")";
285     tab(n + 1, fOutAux);
286     fOutAux << ")";
287 
288     // getParamValue
289     tab(n + 1, fOutAux);
290     fOutAux << "(func $getParamValue (param $dsp i32) (param $index i32) (result " << realStr << ")";
291     tab(n + 2, fOutAux);
292     fOutAux << "(return (" << realStr << ".load (i32.add (local.get $dsp) (local.get $index))))";
293     tab(n + 1, fOutAux);
294     fOutAux << ")";
295 
296     // compute
297     generateCompute(n);
298 
299     // Possibly generate separated functions
300     gGlobal->gWASTVisitor->Tab(n + 1);
301     tab(n + 1, fOutAux);
302     generateComputeFunctions(gGlobal->gWASTVisitor);
303 
304     back(1, fOutAux);
305     fOutAux << ")";
306     tab(n, fOutAux);
307 
308     // JSON generation
309     string json;
310     if (gGlobal->gFloatSize == 1) {
311         json = generateJSON<float>();
312     } else {
313         json = generateJSON<double>();
314     }
315 
316     // Now that DSP structure size is known, concatenate stream parts to produce the final stream
317     string tmp_aux = fOutAux.str();
318     string begin   = tmp_aux.substr(0, begin_memory);
319     string end     = tmp_aux.substr(begin_memory);
320 
321     // Write begining of code stream on *fOut
322     *fOut << begin;
323 
324     // Insert memory generation
325     string json1 = flattenJSON(json);
326     tab(n + 1, *fOut);
327     if (fInternalMemory) {
328         int memory_size = genMemSize(gGlobal->gWASTVisitor->getStructSize(), fNumInputs + fNumOutputs, (int)json1.size());
329         *fOut << "(memory (export \"memory\") ";
330         // Since JSON is written in data segment at offset 0, the memory size
331         // must be computed taking account JSON size and DSP + audio buffer size
332         *fOut << memory_size // initial memory pages
333               << " " << (memory_size + 1000) << ")";  // maximum memory pages number, minimum value is to be extended on JS side for soundfiles
334     } else {
335         // Memory size set by JS code, so use a minimum value that contains
336         // the data segment size (shoud be OK for any JSON)
337         *fOut << "(import \"env\" \"memory\" (memory $0 1))";
338     }
339 
340     // Generate one data segment containing the JSON string starting at offset 0
341     tab(n + 1, *fOut);
342     *fOut << "(data (i32.const 0) \"" << json1 << "\")";
343 
344     // And write end of code stream on *fOut
345     *fOut << end;
346 
347     // Helper code
348 
349     // Generate JSON and getSize
350     tab(n, fHelper);
351     fHelper << "/*\n"
352             << "Code generated with Faust version " << FAUSTVERSION << endl;
353     fHelper << "Compilation options: ";
354     gGlobal->printCompilationOptions(fHelper);
355     fHelper << "\n*/\n";
356 
357     // Generate JSON
358     tab(n, fHelper);
359     string json2 = flattenJSON1(json);
360     fHelper << "function getJSON" << fKlassName << "() {";
361     tab(n + 1, fHelper);
362     fHelper << "return '";
363     fHelper << json2;
364     fHelper << "';";
365     printlines(n + 1, fUICode, fHelper);
366     tab(n, fHelper);
367     fHelper << "}\n";
368 }
369 
generateIntMin()370 DeclareFunInst* WASInst::generateIntMin()
371 {
372     string v1 = gGlobal->getFreshID("v1");
373     string v2 = gGlobal->getFreshID("v2");
374 
375     list<NamedTyped*> args;
376     args.push_back(InstBuilder::genNamedTyped(v1, Typed::kInt32));
377     args.push_back(InstBuilder::genNamedTyped(v2, Typed::kInt32));
378 
379     BlockInst* block = InstBuilder::genBlockInst();
380     block->pushBackInst(InstBuilder::genRetInst(InstBuilder::genSelect2Inst(
381         InstBuilder::genLessThan(InstBuilder::genLoadFunArgsVar(v1), InstBuilder::genLoadFunArgsVar(v2)),
382         InstBuilder::genLoadFunArgsVar(v1), InstBuilder::genLoadFunArgsVar(v2))));
383     // Creates function
384     FunTyped* fun_type = InstBuilder::genFunTyped(args, InstBuilder::genInt32Typed(), FunTyped::kDefault);
385     return InstBuilder::genDeclareFunInst("min_i", fun_type, block);
386 }
387 
generateIntMax()388 DeclareFunInst* WASInst::generateIntMax()
389 {
390     string v1 = gGlobal->getFreshID("v1");
391     string v2 = gGlobal->getFreshID("v2");
392 
393     list<NamedTyped*> args;
394     args.push_back(InstBuilder::genNamedTyped(v1, Typed::kInt32));
395     args.push_back(InstBuilder::genNamedTyped(v2, Typed::kInt32));
396 
397     BlockInst* block = InstBuilder::genBlockInst();
398     block->pushBackInst(InstBuilder::genRetInst(InstBuilder::genSelect2Inst(
399         InstBuilder::genLessThan(InstBuilder::genLoadFunArgsVar(v1), InstBuilder::genLoadFunArgsVar(v2)),
400         InstBuilder::genLoadFunArgsVar(v2), InstBuilder::genLoadFunArgsVar(v1))));
401     // Creates function
402     FunTyped* fun_type = InstBuilder::genFunTyped(args, InstBuilder::genInt32Typed(), FunTyped::kDefault);
403     return InstBuilder::genDeclareFunInst("max_i", fun_type, block);
404 }
405 
406 // Auxiliary functions for shared code in generateCompute
generateComputeAux1(int n)407 void WASTCodeContainer::generateComputeAux1(int n)
408 {
409     tab(n + 1, fOutAux);
410     fOutAux << "(func $compute (param $dsp i32) (param $count i32) (param $inputs i32) (param $outputs i32)";
411     tab(n + 2, fOutAux);
412     gGlobal->gWASTVisitor->Tab(n + 2);
413 }
414 
generateComputeAux2(BlockInst * compute_block,int n)415 void WASTCodeContainer::generateComputeAux2(BlockInst* compute_block, int n)
416 {
417     DeclareFunInst* int_max_fun = WASInst::generateIntMax();
418     DeclareFunInst* int_min_fun = WASInst::generateIntMin();
419 
420     // Inline "max_i" call
421     compute_block = FunctionCallInliner(int_max_fun).getCode(compute_block);
422 
423     // Inline "min_i" call
424     compute_block = FunctionCallInliner(int_min_fun).getCode(compute_block);
425 
426     // Push the loop in compute block
427     fComputeBlockInstructions->pushBackInst(compute_block);
428 
429     // Put local variables at the begining
430     BlockInst* block = MoveVariablesInFront2().getCode(fComputeBlockInstructions, true);
431 
432     // Remove unecessary cast
433     block = CastRemover().getCode(block);
434 
435     block->accept(gGlobal->gWASTVisitor);
436     back(1, fOutAux);
437     fOutAux << ")";
438 }
439 
generateCompute(int n)440 void WASTScalarCodeContainer::generateCompute(int n)
441 {
442     generateComputeAux1(n);
443 
444     // Loop 'i' variable is moved by bytes
445     BlockInst* compute_block = InstBuilder::genBlockInst();
446     compute_block->pushBackInst(fCurLoop->generateScalarLoop(fFullCount, gGlobal->gLoopVarInBytes));
447 
448     // Generates post DSP loop code
449     compute_block->pushBackInst(fPostComputeBlockInstructions);
450 
451     generateComputeAux2(compute_block, n);
452 }
453 
454 // Vector
WASTVectorCodeContainer(const string & name,int numInputs,int numOutputs,std::ostream * out,bool internal_memory)455 WASTVectorCodeContainer::WASTVectorCodeContainer(const string& name, int numInputs, int numOutputs, std::ostream* out,
456                                                  bool internal_memory)
457     : VectorCodeContainer(numInputs, numOutputs), WASTCodeContainer(name, numInputs, numOutputs, out, internal_memory)
458 {
459     // No array on stack, move all of them in struct
460     gGlobal->gMachineMaxStackSize = -1;
461 }
462 
generateCompute(int n)463 void WASTVectorCodeContainer::generateCompute(int n)
464 {
465     generateComputeAux1(n);
466 
467     // Rename all loop variables name to avoid name clash
468     LoopVariableRenamer loop_renamer;
469     generateComputeAux2(loop_renamer.getCode(fDAGBlock), n);
470 }
471