Skip to content

Commit

Permalink
Merge branch 'main' into chris/448-remove-old-code
Browse files Browse the repository at this point in the history
  • Loading branch information
chrispcampbell authored May 20, 2024
2 parents 6057526 + 9c2f7d1 commit b304005
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 7 deletions.
2 changes: 1 addition & 1 deletion packages/plugin-wasm/docs/interfaces/WasmPluginOptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ with) Emscripten versions 2.0.34 and 3.1.46, among others.
-s EXPORT_ES6=1
-s USE_ES6_IMPORT_META=0
-s ENVIRONMENT='web,webview,worker'
-s EXPORTED_FUNCTIONS=['_malloc','_getMaxOutputIndices','_getInitialTime','_getFinalTime','_getSaveper','_runModelWithBuffers']
-s EXPORTED_FUNCTIONS=['_malloc','_free','_getMaxOutputIndices','_getInitialTime','_getFinalTime','_getSaveper','_runModelWithBuffers']
-s EXPORTED_RUNTIME_METHODS=['cwrap']
```

Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-wasm/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface WasmPluginOptions {
* -s EXPORT_ES6=1
* -s USE_ES6_IMPORT_META=0
* -s ENVIRONMENT='web,webview,worker'
* -s EXPORTED_FUNCTIONS=['_malloc','_getMaxOutputIndices','_getInitialTime','_getFinalTime','_getSaveper','_runModelWithBuffers']
* -s EXPORTED_FUNCTIONS=['_malloc','_free','_getMaxOutputIndices','_getInitialTime','_getFinalTime','_getSaveper','_runModelWithBuffers']
* -s EXPORTED_RUNTIME_METHODS=['cwrap']
* ```
*/
Expand Down
25 changes: 22 additions & 3 deletions packages/plugin-wasm/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
// Copyright (c) 2022 Climate Interactive / New Venture Fund

import { existsSync } from 'fs'
import { existsSync, mkdirSync } from 'fs'
import { writeFile } from 'fs/promises'

import { basename, dirname, join as joinPath } from 'path'

import { findUp } from 'find-up'

import type { BuildContext, Plugin } from '@sdeverywhere/build'
import type { BuildContext, ModelSpec, Plugin } from '@sdeverywhere/build'

import type { WasmPluginOptions } from './options'
import { sdeNameForVensimVarName } from './var-names'

export function wasmPlugin(options?: WasmPluginOptions): Plugin {
return new WasmPlugin(options)
Expand All @@ -16,6 +19,20 @@ export function wasmPlugin(options?: WasmPluginOptions): Plugin {
class WasmPlugin implements Plugin {
constructor(private readonly options?: WasmPluginOptions) {}

async preGenerate(context: BuildContext, modelSpec: ModelSpec): Promise<void> {
// Ensure that the build directory exists before we generate a file into it
const buildDir = joinPath(context.config.prepDir, 'build')
if (!existsSync(buildDir)) {
mkdirSync(buildDir, { recursive: true })
}

// Write a file that will be folded into the generated Wasm module
const outputVarsFile = joinPath(buildDir, 'processed_outputs.js')
const outputVarIds = modelSpec.outputs.map(o => sdeNameForVensimVarName(o.varName))
const content = `Module["outputVarIds"] = ${JSON.stringify(outputVarIds)};`
await writeFile(outputVarsFile, content)
}

async postGenerateC(context: BuildContext, cContent: string): Promise<string> {
context.log('info', ' Generating WebAssembly module')

Expand Down Expand Up @@ -99,6 +116,8 @@ async function buildWasm(
addInput('macros.c')
addInput('model.c')
addInput('vensim.c')
addArg('--pre-js')
addArg('build/processed_outputs.js')
addArg('-Ibuild')
addArg('-o')
addArg(outputJsPath)
Expand Down Expand Up @@ -128,7 +147,7 @@ async function buildWasm(
// and Node.js contexts (tested in Emscripten 2.0.34 and 3.1.46).
addFlag(`ENVIRONMENT='web,webview,worker'`)
addFlag(
`EXPORTED_FUNCTIONS=['_malloc','_getMaxOutputIndices','_getInitialTime','_getFinalTime','_getSaveper','_runModelWithBuffers']`
`EXPORTED_FUNCTIONS=['_malloc','_free','_getMaxOutputIndices','_getInitialTime','_getFinalTime','_getSaveper','_runModelWithBuffers']`
)
addFlag(`EXPORTED_RUNTIME_METHODS=['cwrap']`)
}
Expand Down
46 changes: 46 additions & 0 deletions packages/plugin-wasm/src/var-names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) 2024 Climate Interactive / New Venture Fund

/**
* Helper function that converts a Vensim variable or subscript name
* into a valid C identifier as used by SDE.
* TODO: Import helper function from `compile` package instead
*/
function sdeNameForVensimName(name: string): string {
return (
'_' +
name
.trim()
.replace(/"/g, '_')
.replace(/\s+!$/g, '!')
.replace(/\s/g, '_')
.replace(/,/g, '_')
.replace(/-/g, '_')
.replace(/\./g, '_')
.replace(/\$/g, '_')
.replace(/'/g, '_')
.replace(/&/g, '_')
.replace(/%/g, '_')
.replace(/\//g, '_')
.replace(/\|/g, '_')
.toLowerCase()
)
}

/**
* Helper function that converts a Vensim variable name (possibly containing
* subscripts) into a valid C identifier as used by SDE.
* TODO: Import helper function from `compile` package instead
*/
export function sdeNameForVensimVarName(varName: string): string {
const m = varName.match(/([^[]+)(?:\[([^\]]+)\])?/)
if (!m) {
throw new Error(`Invalid Vensim name: ${varName}`)
}
let id = sdeNameForVensimName(m[1])
if (m[2]) {
const subscripts = m[2].split(',').map(x => sdeNameForVensimName(x))
id += `[${subscripts.join('][')}]`
}

return id
}
3 changes: 2 additions & 1 deletion packages/runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,14 @@ $ emcc \
build/<mymodel>.c build/macros.c build/model.c build/vensim.c \
-Ibuild -o ./output/<mymodel>.js -Wall -Os \
-s STRICT=1 -s MALLOC=emmalloc -s FILESYSTEM=0 -s MODULARIZE=1 \
-s EXPORTED_FUNCTIONS="['_malloc','_getInitialTime','_getFinalTime','_getSaveper','_runModelWithBuffers']" \
-s EXPORTED_FUNCTIONS="['_malloc','_free','_getInitialTime','_getFinalTime','_getSaveper','_runModelWithBuffers']" \
-s EXPORTED_RUNTIME_METHODS="['cwrap']"
```
Note that the generated module must export the following functions at minimum:
- `_malloc`
- `_free`
- `_getInitialTime`
- `_getFinalTime`
- `_getSaveper`
Expand Down
13 changes: 12 additions & 1 deletion tests/integration/impl-var-access/run-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,23 @@ async function createSynchronousRunner() {
// fix the generated `wasm-model.js` file so that it works for either ESM or CommonJS.
global.__dirname = '.'

// Load the generated Wasm module and verify that it exposes `outputVarIds`
const wasmModule = await initWasm()
const wasmResult = initWasmModelAndBuffers(wasmModule, 1, ['_z', '_d[_a1]'])
const actualVarIds = wasmModule.outputVarIds || []
const expectedVarIds = ['_z', '_d[_a1]']
if (actualVarIds.length !== expectedVarIds.length || !actualVarIds.every((v, i) => v === expectedVarIds[i])) {
throw new Error(
`Test failed: outputVarIds [${actualVarIds}] in generated Wasm module don't match expected values [${expectedVarIds}]`
)
}

// Initialize the synchronous `ModelRunner` that drives the Wasm model
const wasmResult = initWasmModelAndBuffers(wasmModule, 1, wasmModule.outputVarIds)
return createWasmModelRunner(wasmResult)
}

async function createAsynchronousRunner() {
// Initialize the asynchronous `ModelRunner` that drives the Wasm model
const modelWorkerJs = await readFile(joinPath('sde-prep', 'worker.js'), 'utf8')
return await spawnAsyncModelRunner({ source: modelWorkerJs })
}
Expand Down

0 comments on commit b304005

Please sign in to comment.