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