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