feat(compiler): Add support for targeting WebAssembly.

The working principle is similar as before with x86, but instead of outputting .asm, it outputs .wat. This can be compiled into .wasm by using the wat2wasm assembler.
This commit is contained in:
Veikko Sariola
2020-12-26 23:16:18 +02:00
parent 7e4bcf18e4
commit e4490faa2e
32 changed files with 2138 additions and 170 deletions

View File

@ -8,11 +8,23 @@ function(regression_test testname)
add_custom_command(
OUTPUT ${asmfile}
COMMAND ${compilecmd} -arch=${arch} -o ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${source}
DEPENDS ${source} ${compilecmd} ${templates}
DEPENDS ${source} ${x86templates} sointu-compiler
)
add_executable(${testname} test_renderer.c ${asmfile})
target_compile_definitions(${testname} PUBLIC TEST_HEADER=<${testname}.h>)
if (NODE AND WAT2WASM AND NOT ${testname} MATCHES "sample")
set(wasmfile ${CMAKE_CURRENT_BINARY_DIR}/${testname}.wasm)
set(watfile ${CMAKE_CURRENT_BINARY_DIR}/${testname}.wat)
set(wasmtarget wasm_${testname})
add_custom_target(${wasmtarget} ALL
COMMAND ${compilecmd} -arch=wasm -o ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${source} && ${WAT2WASM} --enable-bulk-memory -o ${wasmfile} ${watfile}
SOURCES "${source}" "${wasmtemplates}"
DEPENDS sointu-compiler
)
add_test(${wasmtarget} ${NODE} ${CMAKE_CURRENT_SOURCE_DIR}/wasm_test_renderer.es6 ${wasmfile} ${CMAKE_CURRENT_SOURCE_DIR}/expected_output/${testname}.raw)
endif()
else()
set(source ${ARGV3})
add_executable(${testname} ${source} test_renderer.c)

View File

@ -34,6 +34,17 @@ int main(int argc, char* argv[]) {
su_render_song(buf);
#if defined (_WIN32)
CreateDirectory(actual_output_folder, NULL);
#else
mkdir(actual_output_folder, 0777);
#endif
snprintf(filename, sizeof filename, "%s%s%s", actual_output_folder, test_name, ".raw");
f = fopen(filename, "wb");
fwrite((void*)buf, sizeof(SUsample), SU_BUFFER_LENGTH, f);
fclose(f);
snprintf(filename, sizeof filename, "%s%s%s", expected_output_folder, test_name, ".raw");
f = fopen(filename, "rb");
@ -79,16 +90,6 @@ int main(int argc, char* argv[]) {
printf("Warning: Sointu rendered almost correct wave, but a small maximum error of %f\n",max_diff);
}
#if defined (_WIN32)
CreateDirectory(actual_output_folder, NULL);
#else
mkdir(actual_output_folder, 0777);
#endif
snprintf(filename, sizeof filename, "%s%s%s", actual_output_folder, test_name, ".raw");
f = fopen(filename, "wb");
fwrite((void*)buf, sizeof(SUsample), SU_BUFFER_LENGTH, f);
fclose(f);
return 0;
fail:

View File

@ -0,0 +1,95 @@
'use strict';
const fs = require('fs');
const path = require('path');
const { exit } = require('process');
if (process.argv.length <= 3) {
console.log("Usage: wasm_test_renderer.es6 path/to/compiled_wasm_song.wasm path/to/expected_output.raw")
console.log("The test renderer needs to know the location and length of the output buffer in wasm memory; remember to sointu-compile the .wat with TBW")
exit(2)
}
(async () => {
var file,wasm,instance
try {
file = fs.readFileSync(process.argv[2])
} catch (err) {
console.error("could not read wasmfile "+process.argv[2]+": "+err);
return 1
}
try {
wasm = await WebAssembly.compile(file);
} catch (err) {
console.error("could not compile wasmfile "+process.argv[2]+": "+err);
return 1
}
try {
instance = await WebAssembly.instantiate(wasm,{m:Math});
} catch (err) {
console.error("could not instantiate wasmfile "+process.argv[2]+": "+err);
return 1
}
let gotBuffer = instance.exports.t.value ?
new Int16Array(instance.exports.m.buffer,instance.exports.s.value,instance.exports.l.value/2) :
new Float32Array(instance.exports.m.buffer,instance.exports.s.value,instance.exports.l.value/4);
const gotFileName = path.join(path.parse(process.argv[2]).dir,"wasm_got_" + path.parse(process.argv[3]).name+".raw");
try {
const gotByteBuffer = Buffer.from(instance.exports.m.buffer,instance.exports.s.value,instance.exports.l.value);
fs.writeFileSync(gotFileName, gotByteBuffer);
} catch (err) {
console.error("could not save the buffer we got to disk "+gotFileName+": "+err);
return 1
}
const expectedFile = fs.readFileSync(process.argv[3]);
let expectedBuffer = instance.exports.t.value ?
new Int16Array(expectedFile.buffer, expectedFile.offset, expectedFile.byteLength/2) :
new Float32Array(expectedFile.buffer, expectedFile.offset, expectedFile.byteLength/4);
if (gotBuffer.length < expectedBuffer.length)
{
console.error("got shorter buffer than expected");
return 1
}
if (gotBuffer.length > expectedBuffer.length)
{
console.error("got longer buffer than expected");
return 1
}
let margin = 1e-2 * (instance.exports.t.value ? 32767 : 1);
var firstError = true, firstErrorPos, errorCount = 0
// we still have occasional sample wrong here or there. We only consider this a true error
// if the total number of errors is too high
for (var i = 2; i < gotBuffer.length-2; i++) {
// Pulse oscillators with their sharp changes can sometimes be one sample late
// due to rounding errors, causing the test fail. So, we test three samples
// and if none match, then this sample is really wrong. Note that this is stereo
// buffer so -2 index is the previous sample.
// Also, we're pretty liberal on the accuracy, as small rounding errors
// in frequency cause tests fails as the waves developed a phase shift over time
// (or rounding errors in delay buffers etc.)
if (Math.abs(gotBuffer[i] - expectedBuffer[i-2]) > margin &&
Math.abs(gotBuffer[i] - expectedBuffer[i]) > margin &&
Math.abs(gotBuffer[i] - expectedBuffer[i+2]) > margin) {
if (firstError) {
firstErrorPos = i
firstError = false
}
errorCount++
}
if (errorCount > 100) {
console.error("got different buffer than expected. First error at: "+(firstErrorPos/2|0)+(firstErrorPos%1," right"," left"));
return 1;
}
}
return 0;
})().then(retval => exit(retval));