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