This commit is contained in:
2024-11-09 21:57:08 -06:00
parent a1790b8977
commit d5e0b2ad79
17 changed files with 3696 additions and 20 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.cache/
build/
compile_commands.json
/dist/
node_modules/

3
.prettierignore Normal file
View File

@@ -0,0 +1,3 @@
pffft/
mini-odeint/
pnpm-lock.yaml

48
CMakeLists.txt Normal file
View File

@@ -0,0 +1,48 @@
cmake_minimum_required(VERSION 3.20)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
set(EMSCRIPTEN_ROOT $ENV{EMSDK})
if(NOT EMSCRIPTEN_ROOT)
set(EMSCRIPTEN_ROOT /usr/lib/emscripten)
endif()
set(EMSCRIPTEN_ROOT
"${EMSCRIPTEN_ROOT}"
CACHE PATH "Emscripten SDK path")
set(CMAKE_TOOLCHAIN_FILE
"${EMSCRIPTEN_ROOT}/cmake/Modules/Platform/Emscripten.cmake"
CACHE FILEPATH "Emscripten toolchain file")
project(ecgsyn.js)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 20)
set(PFFFT_USE_TYPE_FLOAT
OFF
CACHE BOOL "activate pffft float" FORCE)
include_directories(BEFORE SYSTEM compat)
add_compile_options(-flto -msimd128 -mavx)
add_link_options(-flto)
add_subdirectory(mini-odeint EXCLUDE_FROM_ALL)
add_subdirectory(pffft EXCLUDE_FROM_ALL)
add_executable(ecgsyn ecgsyn.cpp)
target_compile_options(ecgsyn PRIVATE)
target_link_options(
ecgsyn
PRIVATE
-flto
-sEXPORTED_FUNCTIONS=_ecgsyn
--no-entry
-sSTRICT
-sNO_ASSERTIONS
-sNO_FILESYSTEM
-sMALLOC=emmalloc)
target_link_libraries(ecgsyn PRIVATE PFFFT)
set_target_properties(ecgsyn PROPERTIES SUFFIX ".wasm")

2010
compat/avxintrin.h Normal file

File diff suppressed because it is too large Load Diff

29
demo/index.html Normal file
View File

@@ -0,0 +1,29 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Untitled</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<!-- Place favicon.ico in the root directory -->
</head>
<script type="module">
import load from "../dist/index.js";
let ecgsyn = await load();
window.ecgsn = ecgsyn;
ecgsyn.ecgsyn();
</script>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">
You are using an <strong>outdated</strong> browser. Please
<a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
</p>
<![endif]-->
</body>
</html>

9
ecgsyn.cpp Normal file
View File

@@ -0,0 +1,9 @@
#include <cstdio>
extern "C" {
int ecgsyn() {
std::printf("hello world\n");
return 69;
}
}

29
eslint.config.js Normal file
View File

@@ -0,0 +1,29 @@
// @ts-check
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config(
{
ignores: ["dist/**"],
},
eslint.configs.recommended,
...tseslint.configs.strict,
...tseslint.configs.stylistic,
{
files: ["**/*.js"],
},
{
files: ["**/*.ts"],
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
varsIgnorePattern: "^_",
argsIgnorePattern: "^_",
},
],
"@typescript-eslint/ban-ts-comment": "off",
},
},
);

22
index.html Normal file
View File

@@ -0,0 +1,22 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Untitled</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<!-- Place favicon.ico in the root directory -->
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">
You are using an <strong>outdated</strong> browser. Please
<a href="http://browsehappy.com/">upgrade your browser</a> to improve
your experience.
</p>
<![endif]-->
</body>
</html>

View File

@@ -3,26 +3,33 @@ cmake_minimum_required(VERSION 3.20)
project(mini-odeint) project(mini-odeint)
option(FORCE_FETCH_CATCH2 "Force fetching Catch2" OFF) option(FORCE_FETCH_CATCH2 "Force fetching Catch2" OFF)
option(MINI_ODEINT_BUILD_TESTS "Build tests" OFF)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Catch2 3 QUIET) if(MINI_ODEINT_BUILD_TESTS)
find_package(Catch2 3 QUIET)
if(NOT TARGET Catch2::Catch2WithMain OR FORCE_FETCH_CATCH2) if(NOT TARGET Catch2::Catch2WithMain OR FORCE_FETCH_CATCH2)
include(FetchContent) include(FetchContent)
FetchContent_Declare( FetchContent_Declare(
Catch2 Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.4.0) GIT_TAG v3.4.0)
FetchContent_MakeAvailable(Catch2) FetchContent_MakeAvailable(Catch2)
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
endif()
include(CTest)
include(Catch)
add_executable(tests mini-odeint-tests.cpp include/mini-odeint.hpp)
target_include_directories(tests PRIVATE include)
target_link_libraries(tests PRIVATE Catch2::Catch2WithMain)
catch_discover_tests(tests)
endif() endif()
include(CTest) add_library(mini-odeint INTERFACE include/mini-odeint.hpp)
include(Catch) target_include_directories(mini-odeint INTERFACE include)
add_executable(tests mini-odeint-tests.cpp mini-odeint.hpp)
target_link_libraries(tests PRIVATE Catch2::Catch2WithMain)
catch_discover_tests(tests)

29
package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"type": "module",
"name": "ecgsyn.js",
"description": "ECGSyn demo",
"version": "1.0.0",
"author": "John Luebs <john@luebs.org>",
"license": "MIT",
"module": "dist/ecgsyn.js",
"exports": {
".": "./dist/ecgsyn.js",
"./ecgsyn.wasm": "./dist/ecgsyn.wasm"
},
"scripts": {
"build": "cmake --build build && tsc && cp -f build/ecgsyn.wasm ./dist",
"typecheck": "tsc --noEmit",
"lint": "eslint .",
"format": "prettier --write \"*.{js,ts,json,css,yml,yaml}\" \"**/*.{js,ts,json,css,yml.yaml}\"",
"serve": "http-server -c-1",
"test": "vitest"
},
"devDependencies": {
"@eslint/js": "^9.14.0",
"http-server": "^14.1.1",
"prettier": "^3.3.3",
"typescript": "^5.6.3",
"typescript-eslint": "^8.13.0"
},
"packageManager": "pnpm@9.12.3+sha512.cce0f9de9c5a7c95bef944169cc5dfe8741abfb145078c0d508b868056848a87c81e626246cb60967cbd7fd29a6c062ef73ff840d96b3c86c40ac92cf4a813ee"
}

View File

@@ -1,11 +1,11 @@
function(target_activate_cxx_compiler_warnings target) function(target_activate_cxx_compiler_warnings target)
target_compile_options(${target} PRIVATE $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra -pedantic>) target_compile_options(${target} PRIVATE $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra -pedantic>)
target_compile_options(${target} PRIVATE $<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra -pedantic>) target_compile_options(${target} PRIVATE $<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra -pedantic -Wno-keyword-macro>)
endfunction() endfunction()
function(target_activate_c_compiler_warnings target) function(target_activate_c_compiler_warnings target)
target_compile_options(${target} PRIVATE $<$<C_COMPILER_ID:GNU>:-Wall -Wextra -pedantic>) target_compile_options(${target} PRIVATE $<$<C_COMPILER_ID:GNU>:-Wall -Wextra -pedantic>)
target_compile_options(${target} PRIVATE $<$<C_COMPILER_ID:Clang>:-Wall -Wextra -pedantic>) target_compile_options(${target} PRIVATE $<$<C_COMPILER_ID:Clang>:-Wall -Wextra -pedantic -Wno-keyword-macro>)
endfunction() endfunction()

View File

@@ -194,10 +194,10 @@ extern "C" {
void pffft_zconvolve_no_accu(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling); void pffft_zconvolve_no_accu(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling);
/* return 4 or 1 wether support SSE/NEON/Altivec instructions was enabled when building pffft.c */ /* return 4 or 1 wether support SSE/NEON/Altivec instructions was enabled when building pffft.c */
int pffft_simd_size(); int pffft_simd_size(void);
/* return string identifier of used architecture (SSE/NEON/Altivec/..) */ /* return string identifier of used architecture (SSE/NEON/Altivec/..) */
const char * pffft_simd_arch(); const char * pffft_simd_arch(void);
/* following functions are identical to the pffftd_ functions */ /* following functions are identical to the pffftd_ functions */

1329
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

19
prettier.config.js Normal file
View File

@@ -0,0 +1,19 @@
// @ts-check
/** @type {import("prettier").Config} */
export default {
semi: true,
trailingComma: "all",
singleQuote: false,
printWidth: 120,
endOfLine: "auto",
tabWidth: 4,
useTabs: false,
overrides: [
{
files: "*.json",
options: {
parser: "json",
},
},
],
};

116
src/index.ts Normal file
View File

@@ -0,0 +1,116 @@
export interface Exports extends WebAssembly.Exports {
memory: WebAssembly.Memory;
_initialize(): void;
ecgsyn(): number;
}
let exports: Exports;
class WASI {
private instance?: WebAssembly.Instance;
private ESUCCESS = 0;
private ENOSYS = 52;
private nameSpaces: Record<string, Record<string, CallableFunction | undefined>> = {
wasi_snapshot_preview1: {
fd_write: this.fd_write,
},
};
constructor() {
for (const ns of Object.keys(this.nameSpaces)) {
const nameSpace = this.nameSpaces[ns];
for (const fn of Object.keys(nameSpace)) {
let func = nameSpace[fn] || this.nosys(fn);
func = func.bind(this);
nameSpace[fn] = func;
}
}
}
initialize(instance: WebAssembly.Instance): void {
this.instance = instance;
(instance.exports._initialize as CallableFunction)();
}
get imports(): WebAssembly.Imports {
return this.nameSpaces as WebAssembly.Imports;
}
private get memory(): WebAssembly.Memory {
if (!this.instance) {
throw new Error("Instance not initialized");
}
return this.instance.exports.memory as WebAssembly.Memory;
}
private getDataview(): DataView {
if (!this.instance) {
throw new Error("Instance not initialized");
}
return new DataView((this.instance.exports.memory as WebAssembly.Memory).buffer);
}
private nosys(name: string): CallableFunction {
return (...args: number[]): number => {
console.error(`Unimplemented call to ${name}(${args.toString()})`);
return this.ENOSYS;
};
}
private fd_write(fd: number, iovs: number, iovsLen: number, nwritten: number): number {
const view = this.getDataview();
const memory = this.memory;
const buffers: Uint8Array[] = [];
let totalLen = 0;
for (let i = 0; i < iovsLen; i++) {
const iov = iovs + i * 8;
const data = view.getUint32(iov, true);
const len = view.getUint32(iov + 4, true);
if (len === 0) continue;
buffers.push(new Uint8Array(memory.buffer, data, len));
totalLen += len;
}
if (!totalLen) {
view.setUint32(nwritten, 0, true);
return this.ESUCCESS;
}
let buffer = new Uint8Array(totalLen);
let offset = 0;
for (const b of buffers) {
buffer.set(b, offset);
offset += b.length;
}
if (buffer.length > 0 && buffer[buffer.length - 1] === 10) {
buffer = buffer.subarray(0, buffer.length - 1);
}
const string = new TextDecoder("utf-8").decode(buffer);
if (fd === 1) console.log(string);
else console.error(string);
view.setUint32(nwritten, totalLen, true);
return this.ESUCCESS;
}
}
export default async function load(): Promise<Exports> {
if (!exports) {
const url = new URL("./ecgsyn.wasm", import.meta.url);
const wasi = new WASI();
const { instance } = await WebAssembly.instantiateStreaming(fetch(url.href), wasi.imports);
exports = instance.exports as Exports;
wasi.initialize(instance);
}
return exports;
}

21
tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"strict": true,
"sourceMap": true,
"declaration": true,
"declarationDir": "./dist/types/",
"noImplicitAny": true,
"esModuleInterop": true,
"downlevelIteration": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"isolatedModules": true,
"isolatedDeclarations": true
},
"include": ["src"],
"exclude": ["src/**/*.test.ts"]
}