mirror of
https://github.com/LukeHagar/wasm-overhead-research.git
synced 2025-12-06 04:22:06 +00:00
chore: tcr analysis
This commit is contained in:
469
.mise.toml
Normal file
469
.mise.toml
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
[tools]
|
||||||
|
go = "1.24"
|
||||||
|
"rust" = { version = "1.83.0" }
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Individual Implementation Build Tasks
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Basic implementation - Go
|
||||||
|
[tasks."build:basic:go"]
|
||||||
|
description = "Build basic implementation with Go"
|
||||||
|
env.GOOS = "js"
|
||||||
|
env.GOARCH = "wasm"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building basic implementation with Go..."
|
||||||
|
mkdir -p assets/wasm
|
||||||
|
go mod tidy > /dev/null 2>&1
|
||||||
|
go build -trimpath -o main.wasm implementations/basic/main.go
|
||||||
|
gzip -9 -c main.wasm > assets/wasm/lib.wasm.gz
|
||||||
|
rm main.wasm
|
||||||
|
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" assets/wasm/wasm_exec.js
|
||||||
|
echo "✅ Basic Go build complete: $(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Basic implementation - TinyGo
|
||||||
|
[tasks."build:basic:tinygo"]
|
||||||
|
description = "Build basic implementation with TinyGo"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building basic implementation with TinyGo..."
|
||||||
|
if ! command -v tinygo >/dev/null 2>&1; then
|
||||||
|
echo "❌ Error: TinyGo is not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p assets/wasm
|
||||||
|
go mod tidy > /dev/null 2>&1
|
||||||
|
tinygo build -target wasm -o main.wasm implementations/basic/main.go
|
||||||
|
gzip -9 -c main.wasm > assets/wasm/lib.wasm.gz
|
||||||
|
rm main.wasm
|
||||||
|
cp "$(tinygo env TINYGOROOT)/targets/wasm_exec.js" assets/wasm/wasm_exec.js
|
||||||
|
echo "✅ Basic TinyGo build complete: $(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Goja implementation - Go only (doesn't work with TinyGo)
|
||||||
|
[tasks."build:goja:go"]
|
||||||
|
description = "Build goja implementation with Go"
|
||||||
|
env.GOOS = "js"
|
||||||
|
env.GOARCH = "wasm"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building goja implementation with Go..."
|
||||||
|
mkdir -p assets/wasm
|
||||||
|
go mod tidy > /dev/null 2>&1
|
||||||
|
go build -trimpath -o main.wasm implementations/goja/main.go
|
||||||
|
gzip -9 -c main.wasm > assets/wasm/lib.wasm.gz
|
||||||
|
rm main.wasm
|
||||||
|
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" assets/wasm/wasm_exec.js
|
||||||
|
echo "✅ Goja Go build complete: $(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Javy implementation
|
||||||
|
[tasks."build:javy"]
|
||||||
|
description = "Build Javy implementation (JavaScript to WASM)"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building Javy implementation..."
|
||||||
|
if ! command -v javy >/dev/null 2>&1; then
|
||||||
|
echo "❌ Error: Javy CLI is not installed or not on PATH"
|
||||||
|
echo "Please install the Javy CLI from https://github.com/bytecodealliance/javy"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p assets/wasm
|
||||||
|
cd implementations/javy
|
||||||
|
javy emit-plugin -o plugin.wasm
|
||||||
|
javy build -C dynamic -C plugin=plugin.wasm -o transform_dynamic.wasm transform.js
|
||||||
|
cd ../..
|
||||||
|
gzip -9 -c implementations/javy/transform_dynamic.wasm > assets/wasm/lib.wasm.gz
|
||||||
|
echo "✅ Javy build complete: $(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Porffor implementation
|
||||||
|
[tasks."build:porffor"]
|
||||||
|
description = "Build Porffor implementation (AOT JavaScript to WASM)"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building Porffor implementation..."
|
||||||
|
mkdir -p assets/wasm
|
||||||
|
cd implementations/porffor
|
||||||
|
npx porf wasm transform.js transform.wasm
|
||||||
|
cd ../..
|
||||||
|
gzip -9 -c implementations/porffor/transform.wasm > assets/wasm/lib.wasm.gz
|
||||||
|
echo "✅ Porffor build complete: $(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# QuickJS implementation
|
||||||
|
[tasks."build:quickjs"]
|
||||||
|
description = "Build QuickJS implementation (Rust + QuickJS)"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building QuickJS implementation..."
|
||||||
|
if ! command -v cargo >/dev/null 2>&1; then
|
||||||
|
echo "❌ Error: Rust/Cargo is not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p assets/wasm
|
||||||
|
cd implementations/quickjs
|
||||||
|
cargo build --target wasm32-wasip1 --release
|
||||||
|
cd ../..
|
||||||
|
gzip -9 -c implementations/quickjs/target/wasm32-wasip1/release/quickjs_transform.wasm > assets/wasm/lib.wasm.gz
|
||||||
|
echo "✅ QuickJS build complete: $(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# AssemblyScript implementation
|
||||||
|
[tasks."build:assemblyscript"]
|
||||||
|
description = "Build AssemblyScript implementation with json-as"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building AssemblyScript implementation..."
|
||||||
|
if ! command -v npm >/dev/null 2>&1; then
|
||||||
|
echo "❌ Error: npm is not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p assets/wasm
|
||||||
|
cd implementations/assemblyscript
|
||||||
|
npm install --legacy-peer-deps > /dev/null 2>&1
|
||||||
|
npm run asbuild > /dev/null 2>&1
|
||||||
|
cd ../..
|
||||||
|
gzip -9 -c implementations/assemblyscript/build/release.wasm > assets/wasm/lib.wasm.gz
|
||||||
|
echo "✅ AssemblyScript build complete: $(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# AssemblyScript optimized implementation
|
||||||
|
[tasks."build:assemblyscript:optimized"]
|
||||||
|
description = "Build optimized AssemblyScript implementation"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building optimized AssemblyScript implementation..."
|
||||||
|
mkdir -p assets/wasm
|
||||||
|
cd implementations/assemblyscript
|
||||||
|
npm install --legacy-peer-deps > /dev/null 2>&1
|
||||||
|
npm run asbuild > /dev/null 2>&1
|
||||||
|
if command -v wasm-opt >/dev/null 2>&1; then
|
||||||
|
wasm-opt -Oz --enable-bulk-memory --strip-debug --strip-dwarf --strip-producers --converge build/release.wasm -o build/release_opt.wasm
|
||||||
|
mv build/release_opt.wasm build/release.wasm
|
||||||
|
fi
|
||||||
|
cd ../..
|
||||||
|
gzip -9 -c implementations/assemblyscript/build/release.wasm > assets/wasm/lib.wasm.gz
|
||||||
|
echo "✅ AssemblyScript optimized build complete: $(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Optimized Build Tasks
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
[tasks."build:basic:go:optimized"]
|
||||||
|
description = "Build basic implementation with optimized Go"
|
||||||
|
env.GOOS = "js"
|
||||||
|
env.GOARCH = "wasm"
|
||||||
|
env.CGO_ENABLED = "0"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building basic implementation with optimized Go..."
|
||||||
|
mkdir -p assets/wasm
|
||||||
|
go mod tidy > /dev/null 2>&1
|
||||||
|
go build -ldflags="-s -w -buildid=" -gcflags="-l=4 -B -C" -trimpath -o main.wasm implementations/basic/main.go
|
||||||
|
if command -v wasm-opt >/dev/null 2>&1; then
|
||||||
|
wasm-opt -Oz --enable-bulk-memory --enable-sign-ext --converge main.wasm -o main_opt.wasm
|
||||||
|
mv main_opt.wasm main.wasm
|
||||||
|
fi
|
||||||
|
gzip -9 -c main.wasm > assets/wasm/lib.wasm.gz
|
||||||
|
rm main.wasm
|
||||||
|
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" assets/wasm/wasm_exec.js
|
||||||
|
echo "✅ Basic optimized Go build complete: $(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
[tasks."build:basic:tinygo:optimized"]
|
||||||
|
description = "Build basic implementation with optimized TinyGo"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building basic implementation with optimized TinyGo..."
|
||||||
|
if ! command -v tinygo >/dev/null 2>&1; then
|
||||||
|
echo "❌ Error: TinyGo is not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p assets/wasm
|
||||||
|
go mod tidy > /dev/null 2>&1
|
||||||
|
tinygo build -target wasm -no-debug -gc=leaking -opt=z -size=full -panic=trap -o main.wasm implementations/basic/main.go
|
||||||
|
if command -v wasm-opt >/dev/null 2>&1; then
|
||||||
|
wasm-opt -Oz --enable-bulk-memory --enable-sign-ext --enable-mutable-globals --converge --all-features main.wasm -o main_opt.wasm
|
||||||
|
mv main_opt.wasm main.wasm
|
||||||
|
fi
|
||||||
|
gzip -9 -c main.wasm > assets/wasm/lib.wasm.gz
|
||||||
|
rm main.wasm
|
||||||
|
cp "$(tinygo env TINYGOROOT)/targets/wasm_exec.js" assets/wasm/wasm_exec.js
|
||||||
|
echo "✅ Basic optimized TinyGo build complete: $(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
[tasks."build:goja:go:optimized"]
|
||||||
|
description = "Build goja implementation with optimized Go"
|
||||||
|
env.GOOS = "js"
|
||||||
|
env.GOARCH = "wasm"
|
||||||
|
env.CGO_ENABLED = "0"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building goja implementation with optimized Go..."
|
||||||
|
mkdir -p assets/wasm
|
||||||
|
go mod tidy > /dev/null 2>&1
|
||||||
|
go build -ldflags="-s -w -buildid=" -gcflags="-l=4 -B -C" -trimpath -o main.wasm implementations/goja/main.go
|
||||||
|
if command -v wasm-opt >/dev/null 2>&1; then
|
||||||
|
wasm-opt -Oz --enable-bulk-memory --enable-sign-ext --converge main.wasm -o main_opt.wasm
|
||||||
|
mv main_opt.wasm main.wasm
|
||||||
|
fi
|
||||||
|
gzip -9 -c main.wasm > assets/wasm/lib.wasm.gz
|
||||||
|
rm main.wasm
|
||||||
|
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" assets/wasm/wasm_exec.js
|
||||||
|
echo "✅ Goja optimized Go build complete: $(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
[tasks."build:porffor:optimized"]
|
||||||
|
description = "Build Porffor implementation with optimization"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building optimized Porffor implementation..."
|
||||||
|
mkdir -p assets/wasm
|
||||||
|
cd implementations/porffor
|
||||||
|
npx porf wasm -O3 transform.js transform.wasm
|
||||||
|
cd ../..
|
||||||
|
gzip -9 -c implementations/porffor/transform.wasm > assets/wasm/lib.wasm.gz
|
||||||
|
echo "✅ Porffor optimized build complete: $(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Individual Test Tasks (with dependencies on builds)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
[tasks."test:basic:go"]
|
||||||
|
description = "Test basic implementation with Go build"
|
||||||
|
depends = ["build:basic:go"]
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Testing basic Go implementation..."
|
||||||
|
npm install > /dev/null 2>&1
|
||||||
|
npx vitest run --reporter=verbose
|
||||||
|
"""
|
||||||
|
|
||||||
|
[tasks."test:basic:tinygo"]
|
||||||
|
description = "Test basic implementation with TinyGo build"
|
||||||
|
depends = ["build:basic:tinygo"]
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Testing basic TinyGo implementation..."
|
||||||
|
npm install > /dev/null 2>&1
|
||||||
|
npx vitest run --reporter=verbose
|
||||||
|
"""
|
||||||
|
|
||||||
|
[tasks."test:goja:go"]
|
||||||
|
description = "Test goja implementation with Go build"
|
||||||
|
depends = ["build:goja:go"]
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Testing goja Go implementation..."
|
||||||
|
npm install > /dev/null 2>&1
|
||||||
|
npx vitest run --reporter=verbose
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
[tasks."test:javy"]
|
||||||
|
description = "Test Javy implementation"
|
||||||
|
depends = ["build:javy"]
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Testing Javy implementation..."
|
||||||
|
node -e "import('./implementations/javy/javy-adapter.js').then(async (javy) => {
|
||||||
|
console.log('🚀 Javy adapter loaded');
|
||||||
|
try {
|
||||||
|
const health = await javy.healthCheck();
|
||||||
|
console.log('💓 Health check:', JSON.parse(health).status);
|
||||||
|
const result = await javy.transformData('{\"name\":\"test\",\"value\":42}');
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
console.log('🔄 Transform test:', parsed.engine === 'javy' ? 'PASS' : 'FAIL');
|
||||||
|
console.log('✅ Javy tests passed!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Javy test failed:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}).catch(console.error);"
|
||||||
|
"""
|
||||||
|
|
||||||
|
[tasks."test:porffor"]
|
||||||
|
description = "Test Porffor implementation"
|
||||||
|
depends = ["build:porffor"]
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Testing Porffor implementation..."
|
||||||
|
if command -v wasmer >/dev/null 2>&1; then
|
||||||
|
cd implementations/porffor
|
||||||
|
if [ -f porffor-wasmer-test.sh ]; then
|
||||||
|
./porffor-wasmer-test.sh
|
||||||
|
else
|
||||||
|
echo "⚠️ Porffor test script not found"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ Wasmer not installed, skipping Porffor WASI test"
|
||||||
|
fi
|
||||||
|
"""
|
||||||
|
|
||||||
|
[tasks."test:quickjs"]
|
||||||
|
description = "Test QuickJS implementation"
|
||||||
|
depends = ["build:quickjs"]
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Testing QuickJS implementation..."
|
||||||
|
if [ -f implementations/quickjs/quickjs-wasi-test.js ]; then
|
||||||
|
node implementations/quickjs/quickjs-wasi-test.js
|
||||||
|
elif command -v wasmer >/dev/null 2>&1; then
|
||||||
|
cd implementations/quickjs
|
||||||
|
if [ -f quickjs-wasmer-test.sh ]; then
|
||||||
|
./quickjs-wasmer-test.sh
|
||||||
|
else
|
||||||
|
echo "⚠️ QuickJS test script not found"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ No QuickJS test available"
|
||||||
|
fi
|
||||||
|
"""
|
||||||
|
|
||||||
|
[tasks."test:assemblyscript"]
|
||||||
|
description = "Test AssemblyScript implementation"
|
||||||
|
depends = ["build:assemblyscript"]
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Testing AssemblyScript implementation..."
|
||||||
|
cd implementations/assemblyscript
|
||||||
|
node test.js
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Master Test Task - depends on all individual tests
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
[tasks.test]
|
||||||
|
description = "Run all working implementation tests"
|
||||||
|
depends = [
|
||||||
|
"test:basic:go",
|
||||||
|
"test:basic:tinygo",
|
||||||
|
"test:goja:go",
|
||||||
|
"test:porffor",
|
||||||
|
"test:quickjs",
|
||||||
|
"test:assemblyscript"
|
||||||
|
]
|
||||||
|
run = """
|
||||||
|
echo "✅ All working tests completed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "Note: Skipped implementations:"
|
||||||
|
echo " - javy (CLI not available via npm package)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Build All Task - builds all implementations
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
[tasks."build:all"]
|
||||||
|
description = "Build all implementations"
|
||||||
|
depends = [
|
||||||
|
"build:basic:go",
|
||||||
|
"build:basic:tinygo",
|
||||||
|
"build:goja:go",
|
||||||
|
"build:javy",
|
||||||
|
"build:porffor",
|
||||||
|
"build:quickjs",
|
||||||
|
"build:assemblyscript"
|
||||||
|
]
|
||||||
|
run = """
|
||||||
|
echo "✅ All builds completed successfully!"
|
||||||
|
"""
|
||||||
|
|
||||||
|
[tasks."build:all:optimized"]
|
||||||
|
description = "Build all implementations with optimization"
|
||||||
|
depends = [
|
||||||
|
"build:basic:go:optimized",
|
||||||
|
"build:basic:tinygo:optimized",
|
||||||
|
"build:goja:go:optimized",
|
||||||
|
"build:porffor:optimized",
|
||||||
|
"build:quickjs",
|
||||||
|
"build:assemblyscript:optimized"
|
||||||
|
]
|
||||||
|
run = """
|
||||||
|
echo "✅ All optimized builds completed successfully!"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Utility Tasks
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
[tasks.clean]
|
||||||
|
description = "Clean build artifacts and node_modules"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Cleaning build artifacts..."
|
||||||
|
rm -rf assets/
|
||||||
|
rm -f main.wasm main.wasm.gz
|
||||||
|
rm -rf node_modules/
|
||||||
|
rm -f implementations/javy/*.wasm
|
||||||
|
rm -f implementations/porffor/*.wasm
|
||||||
|
rm -rf implementations/quickjs/target/
|
||||||
|
echo "✅ Clean complete"
|
||||||
|
"""
|
||||||
|
|
||||||
|
[tasks."size:compare"]
|
||||||
|
description = "Compare sizes of all implementations"
|
||||||
|
run = """
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building and comparing all implementations..."
|
||||||
|
echo ""
|
||||||
|
echo "| Implementation | Compiler | Size (gzipped) |"
|
||||||
|
echo "|----------------|----------|----------------|"
|
||||||
|
|
||||||
|
# Basic - Go
|
||||||
|
mise run build:basic:go > /dev/null 2>&1
|
||||||
|
printf "| Basic | Go | %s |\n" "$(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
|
||||||
|
# Basic - Go Optimized
|
||||||
|
mise run build:basic:go:optimized > /dev/null 2>&1
|
||||||
|
printf "| Basic | Go (opt) | %s |\n" "$(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
|
||||||
|
# Basic - TinyGo
|
||||||
|
mise run build:basic:tinygo > /dev/null 2>&1
|
||||||
|
printf "| Basic | TinyGo | %s |\n" "$(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
|
||||||
|
# Basic - TinyGo Optimized
|
||||||
|
mise run build:basic:tinygo:optimized > /dev/null 2>&1
|
||||||
|
printf "| Basic | TinyGo (opt) | %s |\n" "$(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
|
||||||
|
# Goja - Go
|
||||||
|
mise run build:goja:go > /dev/null 2>&1
|
||||||
|
printf "| Goja | Go | %s |\n" "$(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
|
||||||
|
# Goja - Go Optimized
|
||||||
|
mise run build:goja:go:optimized > /dev/null 2>&1
|
||||||
|
printf "| Goja | Go (opt) | %s |\n" "$(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
|
||||||
|
# Javy
|
||||||
|
if command -v javy >/dev/null 2>&1; then
|
||||||
|
mise run build:javy > /dev/null 2>&1
|
||||||
|
printf "| Javy | Dynamic | %s |\n" "$(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Porffor
|
||||||
|
mise run build:porffor > /dev/null 2>&1
|
||||||
|
printf "| Porffor | AOT | %s |\n" "$(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
|
||||||
|
mise run build:porffor:optimized > /dev/null 2>&1
|
||||||
|
printf "| Porffor | AOT (opt) | %s |\n" "$(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
|
||||||
|
# QuickJS
|
||||||
|
mise run build:quickjs > /dev/null 2>&1
|
||||||
|
printf "| QuickJS | Rust | %s |\n" "$(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
|
||||||
|
# AssemblyScript
|
||||||
|
mise run build:assemblyscript > /dev/null 2>&1
|
||||||
|
printf "| AssemblyScript | AssemblyScript | %s |\n" "$(du -h assets/wasm/lib.wasm.gz | cut -f1)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Size comparison complete!"
|
||||||
|
"""
|
||||||
|
|
||||||
373
Makefile
373
Makefile
@@ -1,387 +1,120 @@
|
|||||||
.PHONY: build build-go build-tinygo build-optimized build-go-optimized build-tinygo-optimized test test-go test-tinygo clean watch help size-comparison
|
.PHONY: build build-go build-tinygo build-optimized build-go-optimized build-tinygo-optimized test test-go test-tinygo clean watch help size-comparison
|
||||||
|
|
||||||
# Default implementation
|
# Default implementation (deprecated): use mise task arguments instead
|
||||||
IMPL ?= basic
|
|
||||||
|
# Pass-through args to mise: use "make <target> <args...>"
|
||||||
|
ARGS := $(filter-out $@,$(MAKECMDGOALS))
|
||||||
|
|
||||||
# Default target
|
# Default target
|
||||||
help:
|
help:
|
||||||
@echo "Available targets:"
|
@echo "This project uses mise for toolchain and tasks."
|
||||||
@echo " build - Build the WASM binary with Go (default)"
|
|
||||||
@echo " build-go - Build the WASM binary with Go"
|
|
||||||
@echo " build-tinygo - Build the WASM binary with TinyGo (smaller size)"
|
|
||||||
@echo " build-optimized - Build with maximum Go optimization + wasm-opt"
|
|
||||||
@echo " build-go-optimized - Build with maximum Go optimization + wasm-opt"
|
|
||||||
@echo " build-tinygo-optimized - Build with maximum TinyGo optimization + wasm-opt"
|
|
||||||
@echo " build-javy - Build with Javy (JavaScript-to-WASM)"
|
|
||||||
@echo " build-porffor - Build with Porffor (AOT JavaScript)"
|
|
||||||
@echo " build-quickjs - Build with QuickJS (Rust + JavaScript engine)"
|
|
||||||
@echo " test - Run tests with Go build (default)"
|
|
||||||
@echo " test-go - Run tests with Go build"
|
|
||||||
@echo " test-tinygo - Run tests with TinyGo build"
|
|
||||||
@echo " test-wasmer - Test WASI-compatible implementations with Wasmer"
|
|
||||||
@echo " test-quickjs-wasmer - Test QuickJS implementation with Wasmer"
|
|
||||||
@echo " test-porffor-wasmer - Test Porffor implementation with Wasmer"
|
|
||||||
@echo " clean - Clean build artifacts"
|
|
||||||
@echo " watch - Build in watch mode with Go"
|
|
||||||
@echo " size-comparison - Compare binary sizes for all implementations"
|
|
||||||
@echo " help - Show this help message"
|
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Available implementations:"
|
@echo "Common commands:"
|
||||||
@ls -1 implementations/ 2>/dev/null || echo " No implementations found"
|
@echo " mise install # Install tools (Go 1.23)"
|
||||||
|
@echo " mise tasks # List available tasks"
|
||||||
|
@echo " mise run build <impl> # Build with Go (alias for build-go)"
|
||||||
|
@echo " mise run build-go-optimized <impl> # Build with Go (optimized)"
|
||||||
|
@echo " mise run build-tinygo <impl> # Build with TinyGo"
|
||||||
|
@echo " mise run test <impl> # Test (builds first)"
|
||||||
|
@echo " mise run clean # Clean artifacts"
|
||||||
|
@echo " mise run watch <impl> # Watch build (requires fswatch)"
|
||||||
|
@echo " make build <impl> # Convenience wrapper to mise"
|
||||||
|
@echo " make test <impl> # Convenience wrapper to mise"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Usage: make build IMPL=<implementation>"
|
@echo "Note: Makefile targets are retained for compatibility but logic has moved to mise."
|
||||||
@echo "Example: make build IMPL=basic"
|
|
||||||
@echo "Example: make build-optimized IMPL=goja"
|
|
||||||
|
|
||||||
# Default build uses Go
|
# Default build uses mise task (accepts positional args: make build <impl>)
|
||||||
build: build-go
|
build:
|
||||||
|
@mise run build $(ARGS)
|
||||||
|
|
||||||
# Build the WASM binary with Go
|
# Build the WASM binary with Go
|
||||||
build-go:
|
build-go:
|
||||||
@echo "Building WASM binary with Go ($(IMPL) implementation)..."
|
@mise run build-go $(ARGS)
|
||||||
@if [ ! -f "implementations/$(IMPL)/main.go" ]; then \
|
|
||||||
echo "❌ Error: Implementation '$(IMPL)' not found in implementations/$(IMPL)/main.go"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@mkdir -p assets/wasm
|
|
||||||
@echo "Installing Go dependencies..."
|
|
||||||
@go mod tidy > /dev/null 2>&1
|
|
||||||
@echo "Compiling to WASM with Go..."
|
|
||||||
@GOOS=js GOARCH=wasm go build -trimpath -o main.wasm implementations/$(IMPL)/main.go
|
|
||||||
@echo "Compressing WASM binary..."
|
|
||||||
@gzip -9 -c main.wasm > main.wasm.gz
|
|
||||||
@rm main.wasm
|
|
||||||
@mv main.wasm.gz assets/wasm/lib.wasm.gz
|
|
||||||
@cp "$$(go env GOROOT)/lib/wasm/wasm_exec.js" assets/wasm/wasm_exec.js
|
|
||||||
@echo "✅ Go build complete ($(IMPL)): assets/wasm/lib.wasm.gz ($$(du -h assets/wasm/lib.wasm.gz | cut -f1))"
|
|
||||||
|
|
||||||
# Build the WASM binary with TinyGo
|
# Build the WASM binary with TinyGo
|
||||||
build-tinygo:
|
build-tinygo:
|
||||||
@echo "Building WASM binary with TinyGo ($(IMPL) implementation)..."
|
@mise run build-tinygo $(ARGS)
|
||||||
@if [ ! -f "implementations/$(IMPL)/main.go" ]; then \
|
|
||||||
echo "❌ Error: Implementation '$(IMPL)' not found in implementations/$(IMPL)/main.go"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@mkdir -p assets/wasm
|
|
||||||
@echo "Installing Go dependencies..."
|
|
||||||
@go mod tidy > /dev/null 2>&1
|
|
||||||
@echo "Compiling to WASM with TinyGo..."
|
|
||||||
@if ! command -v tinygo >/dev/null 2>&1; then \
|
|
||||||
echo "❌ Error: TinyGo is not installed. Please install it from https://tinygo.org/getting-started/install/"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@tinygo build -target wasm -o main.wasm implementations/$(IMPL)/main.go
|
|
||||||
@echo "Compressing WASM binary..."
|
|
||||||
@gzip -9 -c main.wasm > main.wasm.gz
|
|
||||||
@rm main.wasm
|
|
||||||
@mv main.wasm.gz assets/wasm/lib.wasm.gz
|
|
||||||
@cp "$$(tinygo env TINYGOROOT)/targets/wasm_exec.js" assets/wasm/wasm_exec.js
|
|
||||||
@echo "✅ TinyGo build complete ($(IMPL)): assets/wasm/lib.wasm.gz ($$(du -h assets/wasm/lib.wasm.gz | cut -f1))"
|
|
||||||
|
|
||||||
# Default optimized build uses Go
|
# Default optimized build uses Go (mise wrapper)
|
||||||
build-optimized: build-go-optimized
|
build-optimized:
|
||||||
|
@mise run build-optimized $(ARGS)
|
||||||
|
|
||||||
# Build the WASM binary with maximum Go optimization + wasm-opt
|
# Build the WASM binary with maximum Go optimization + wasm-opt
|
||||||
build-go-optimized:
|
build-go-optimized:
|
||||||
@echo "Building WASM binary with maximum Go optimization ($(IMPL) implementation)..."
|
@mise run build-go-optimized $(ARGS)
|
||||||
@if [ ! -f "implementations/$(IMPL)/main.go" ]; then \
|
|
||||||
echo "❌ Error: Implementation '$(IMPL)' not found in implementations/$(IMPL)/main.go"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@mkdir -p assets/wasm
|
|
||||||
@echo "Installing Go dependencies..."
|
|
||||||
@go mod tidy > /dev/null 2>&1
|
|
||||||
@echo "Compiling to WASM with maximum Go optimization..."
|
|
||||||
@CGO_ENABLED=0 GOOS=js GOARCH=wasm go build \
|
|
||||||
-ldflags="-s -w -buildid=" \
|
|
||||||
-gcflags="-l=4 -B -C" \
|
|
||||||
-trimpath \
|
|
||||||
-o main_raw.wasm implementations/$(IMPL)/main.go
|
|
||||||
@echo "Raw size: $$(du -h main_raw.wasm | cut -f1)"
|
|
||||||
@echo "Optimizing with wasm-opt..."
|
|
||||||
@if command -v wasm-opt >/dev/null 2>&1; then \
|
|
||||||
wasm-opt -Oz --enable-bulk-memory --enable-sign-ext --converge main_raw.wasm -o main.wasm; \
|
|
||||||
echo "Optimized size: $$(du -h main.wasm | cut -f1)"; \
|
|
||||||
else \
|
|
||||||
echo "⚠️ wasm-opt not found, skipping post-build optimization"; \
|
|
||||||
mv main_raw.wasm main.wasm; \
|
|
||||||
fi
|
|
||||||
@echo "Compressing WASM binary..."
|
|
||||||
@gzip -9 -c main.wasm > main.wasm.gz
|
|
||||||
@rm main.wasm main_raw.wasm 2>/dev/null || true
|
|
||||||
@mv main.wasm.gz assets/wasm/lib.wasm.gz
|
|
||||||
@cp "$$(go env GOROOT)/lib/wasm/wasm_exec.js" assets/wasm/wasm_exec.js
|
|
||||||
@echo "✅ Optimized Go build complete ($(IMPL)): assets/wasm/lib.wasm.gz ($$(du -h assets/wasm/lib.wasm.gz | cut -f1))"
|
|
||||||
|
|
||||||
# Build the WASM binary with maximum TinyGo optimization + wasm-opt
|
# Build the WASM binary with maximum TinyGo optimization + wasm-opt
|
||||||
build-tinygo-optimized:
|
build-tinygo-optimized:
|
||||||
@echo "Building WASM binary with maximum TinyGo optimization ($(IMPL) implementation)..."
|
@mise run build-tinygo-optimized $(ARGS)
|
||||||
@if [ ! -f "implementations/$(IMPL)/main.go" ]; then \
|
|
||||||
echo "❌ Error: Implementation '$(IMPL)' not found in implementations/$(IMPL)/main.go"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@mkdir -p assets/wasm
|
|
||||||
@echo "Installing Go dependencies..."
|
|
||||||
@go mod tidy > /dev/null 2>&1
|
|
||||||
@echo "Compiling to WASM with maximum TinyGo optimization..."
|
|
||||||
@if ! command -v tinygo >/dev/null 2>&1; then \
|
|
||||||
echo "❌ Error: TinyGo is not installed. Please install it from https://tinygo.org/getting-started/install/"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@tinygo build \
|
|
||||||
-o main_raw.wasm \
|
|
||||||
-target wasm \
|
|
||||||
-no-debug \
|
|
||||||
-gc=leaking \
|
|
||||||
-opt=z \
|
|
||||||
-size=full \
|
|
||||||
-panic=trap \
|
|
||||||
implementations/$(IMPL)/main.go
|
|
||||||
@echo "Raw size: $$(du -h main_raw.wasm | cut -f1)"
|
|
||||||
@echo "Optimizing with wasm-opt..."
|
|
||||||
@if command -v wasm-opt >/dev/null 2>&1; then \
|
|
||||||
wasm-opt -Oz --enable-bulk-memory --enable-sign-ext --enable-mutable-globals --converge --all-features main_raw.wasm -o main.wasm; \
|
|
||||||
echo "Optimized size: $$(du -h main.wasm | cut -f1)"; \
|
|
||||||
else \
|
|
||||||
echo "⚠️ wasm-opt not found, skipping post-build optimization"; \
|
|
||||||
mv main_raw.wasm main.wasm; \
|
|
||||||
fi
|
|
||||||
@echo "Compressing WASM binary..."
|
|
||||||
@gzip -9 -c main.wasm > main.wasm.gz
|
|
||||||
@rm main.wasm main_raw.wasm 2>/dev/null || true
|
|
||||||
@mv main.wasm.gz assets/wasm/lib.wasm.gz
|
|
||||||
@cp "$$(tinygo env TINYGOROOT)/targets/wasm_exec.js" assets/wasm/wasm_exec.js
|
|
||||||
@echo "✅ Optimized TinyGo build complete ($(IMPL)): assets/wasm/lib.wasm.gz ($$(du -h assets/wasm/lib.wasm.gz | cut -f1))"
|
|
||||||
|
|
||||||
# Build WASM binary with Javy (JavaScript to WASM) using dynamic linking
|
# Build WASM binary with Javy (JavaScript to WASM) using dynamic linking
|
||||||
build-javy:
|
build-javy:
|
||||||
@if [ "$(IMPL)" != "javy" ]; then \
|
@mise run build-javy $(ARGS)
|
||||||
echo "❌ Error: Javy build only supports IMPL=javy"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "Building WASM binary with Javy ($(IMPL) implementation)..."
|
|
||||||
@mkdir -p assets/wasm
|
|
||||||
@echo "Creating Javy plugin..."
|
|
||||||
@if ! command -v javy >/dev/null 2>&1; then \
|
|
||||||
echo "❌ Error: Javy is not installed. Please install it from https://github.com/bytecodealliance/javy"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@cd implementations/$(IMPL) && javy emit-plugin -o plugin.wasm
|
|
||||||
@echo "Compiling JavaScript to WASM with dynamic linking..."
|
|
||||||
@cd implementations/$(IMPL) && javy build -C dynamic -C plugin=plugin.wasm -o transform_dynamic.wasm transform.js
|
|
||||||
@echo "Plugin size: $$(du -h implementations/$(IMPL)/plugin.wasm | cut -f1)"
|
|
||||||
@echo "Dynamic module size: $$(du -h implementations/$(IMPL)/transform_dynamic.wasm | cut -f1)"
|
|
||||||
@echo "Total size: $$(du -ch implementations/$(IMPL)/plugin.wasm implementations/$(IMPL)/transform_dynamic.wasm | tail -1 | cut -f1)"
|
|
||||||
@echo "Compressing dynamic module..."
|
|
||||||
@gzip -c implementations/$(IMPL)/transform_dynamic.wasm > assets/wasm/lib.wasm.gz
|
|
||||||
@echo "✅ Javy build complete ($(IMPL)): assets/wasm/lib.wasm.gz ($$(du -h assets/wasm/lib.wasm.gz | cut -f1))"
|
|
||||||
|
|
||||||
# Build WASM binary with Javy optimization (dynamic linking is already optimized)
|
# Build WASM binary with Javy optimization (dynamic linking is already optimized)
|
||||||
build-javy-optimized:
|
build-javy-optimized:
|
||||||
@if [ "$(IMPL)" != "javy" ]; then \
|
@mise run build-javy-optimized $(ARGS)
|
||||||
echo "❌ Error: Javy build only supports IMPL=javy"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "Building WASM binary with optimized Javy ($(IMPL) implementation)..."
|
|
||||||
@echo "Note: Javy dynamic linking is already highly optimized (4KB modules)"
|
|
||||||
@echo "Note: wasm-opt corrupts Javy plugins, so using standard dynamic build"
|
|
||||||
@$(MAKE) build-javy IMPL=$(IMPL)
|
|
||||||
@echo "✅ Optimized Javy build complete ($(IMPL)): assets/wasm/lib.wasm.gz ($$(du -h assets/wasm/lib.wasm.gz | cut -f1))"
|
|
||||||
|
|
||||||
# Build WASM binary with Porffor (AOT JavaScript to WASM)
|
# Build WASM binary with Porffor (AOT JavaScript to WASM)
|
||||||
build-porffor:
|
build-porffor:
|
||||||
@if [ "$(IMPL)" != "porffor" ]; then \
|
@mise run build-porffor $(ARGS)
|
||||||
echo "❌ Error: Porffor build only supports IMPL=porffor"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "Building WASM binary with Porffor ($(IMPL) implementation)..."
|
|
||||||
@mkdir -p assets/wasm
|
|
||||||
@echo "Compiling JavaScript to WASM with Porffor AOT compiler..."
|
|
||||||
@if ! command -v porf >/dev/null 2>&1; then \
|
|
||||||
echo "❌ Error: Porffor is not installed. Please install it with: npm install -g porffor@latest"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@cd implementations/$(IMPL) && porf wasm transform.js transform.wasm
|
|
||||||
@echo "Raw size: $$(du -h implementations/$(IMPL)/transform.wasm | cut -f1)"
|
|
||||||
@echo "Compressing WASM binary..."
|
|
||||||
@gzip -c implementations/$(IMPL)/transform.wasm > assets/wasm/lib.wasm.gz
|
|
||||||
@echo "✅ Porffor build complete ($(IMPL)): assets/wasm/lib.wasm.gz ($$(du -h assets/wasm/lib.wasm.gz | cut -f1))"
|
|
||||||
|
|
||||||
# Build WASM binary with Porffor optimization
|
# Build WASM binary with Porffor optimization
|
||||||
build-porffor-optimized:
|
build-porffor-optimized:
|
||||||
@if [ "$(IMPL)" != "porffor" ]; then \
|
@mise run build-porffor-optimized $(ARGS)
|
||||||
echo "❌ Error: Porffor build only supports IMPL=porffor"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "Building WASM binary with optimized Porffor ($(IMPL) implementation)..."
|
|
||||||
@mkdir -p assets/wasm
|
|
||||||
@echo "Compiling JavaScript to WASM with Porffor AOT compiler (max optimization)..."
|
|
||||||
@if ! command -v porf >/dev/null 2>&1; then \
|
|
||||||
echo "❌ Error: Porffor is not installed. Please install it with: npm install -g porffor@latest"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@cd implementations/$(IMPL) && porf wasm -O3 transform.js transform_opt.wasm
|
|
||||||
@echo "Optimized size: $$(du -h implementations/$(IMPL)/transform_opt.wasm | cut -f1)"
|
|
||||||
@echo "Compressing WASM binary..."
|
|
||||||
@gzip -c implementations/$(IMPL)/transform_opt.wasm > assets/wasm/lib.wasm.gz
|
|
||||||
@echo "✅ Optimized Porffor build complete ($(IMPL)): assets/wasm/lib.wasm.gz ($$(du -h assets/wasm/lib.wasm.gz | cut -f1))"
|
|
||||||
|
|
||||||
# Default test uses Go
|
# Default test uses mise task (accepts positional args: make test <impl>)
|
||||||
test: test-go
|
test:
|
||||||
|
@mise run test $(ARGS)
|
||||||
|
|
||||||
# Run tests with Go build (builds first)
|
# Run tests with Go build (mise wrapper)
|
||||||
test-go: build-go
|
test-go:
|
||||||
@echo "Installing Node.js dependencies..."
|
@mise run test-go $(ARGS)
|
||||||
@npm install > /dev/null 2>&1
|
|
||||||
@echo "Running tests with Go build..."
|
|
||||||
@npx vitest run
|
|
||||||
|
|
||||||
# Run tests with TinyGo build (builds first)
|
# Run tests with TinyGo build (mise wrapper)
|
||||||
test-tinygo: build-tinygo
|
test-tinygo:
|
||||||
@echo "Installing Node.js dependencies..."
|
@mise run test-tinygo $(ARGS)
|
||||||
@npm install > /dev/null 2>&1
|
|
||||||
@echo "Running tests with TinyGo build..."
|
|
||||||
@npx vitest run
|
|
||||||
|
|
||||||
# Test Javy implementation directly
|
# Test Javy implementation directly
|
||||||
test-javy:
|
test-javy:
|
||||||
@$(MAKE) build-javy IMPL=javy > /dev/null 2>&1
|
@mise run test-javy $(ARGS)
|
||||||
@echo "Testing Javy implementation..."
|
|
||||||
@node -e "import('./implementations/javy/javy-adapter.js').then(async (javy) => { \
|
|
||||||
console.log('🚀 Javy adapter loaded'); \
|
|
||||||
try { \
|
|
||||||
const health = await javy.healthCheck(); \
|
|
||||||
console.log('💓 Health check:', JSON.parse(health).status); \
|
|
||||||
const result = await javy.transformData('{\"name\":\"test\",\"value\":42}'); \
|
|
||||||
const parsed = JSON.parse(result); \
|
|
||||||
console.log('🔄 Transform test:', parsed.engine === 'javy' ? 'PASS' : 'FAIL'); \
|
|
||||||
console.log('✅ All Javy tests passed!'); \
|
|
||||||
} catch (error) { \
|
|
||||||
console.error('❌ Javy test failed:', error.message); \
|
|
||||||
process.exit(1); \
|
|
||||||
} \
|
|
||||||
}).catch(console.error);" 2>/dev/null
|
|
||||||
|
|
||||||
# Clean build artifacts
|
# Clean build artifacts
|
||||||
clean:
|
clean:
|
||||||
@echo "Cleaning build artifacts..."
|
@mise run clean
|
||||||
@rm -rf assets/
|
|
||||||
@rm -f main.wasm main.wasm.gz
|
|
||||||
@rm -rf node_modules/
|
|
||||||
@echo "✅ Clean complete"
|
|
||||||
|
|
||||||
# Build in watch mode with Go (requires fswatch)
|
# Build in watch mode with Go (requires fswatch)
|
||||||
watch:
|
watch:
|
||||||
@if ! command -v fswatch >/dev/null 2>&1; then \
|
@mise run watch $(ARGS)
|
||||||
echo "❌ Error: fswatch is not installed. Install with: brew install fswatch (macOS) or apt-get install fswatch (Linux)"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "🚀 Starting watch mode with Go ($(IMPL)). Press Ctrl+C to stop."
|
|
||||||
@$(MAKE) build-go IMPL=$(IMPL)
|
|
||||||
@echo "👀 Watching for changes to *.go files..."
|
|
||||||
@fswatch -r -e ".*" -i "\\.go$$" implementations/$(IMPL)/ | while read file; do \
|
|
||||||
echo "🔄 Change detected in $$file"; \
|
|
||||||
$(MAKE) build-go IMPL=$(IMPL); \
|
|
||||||
done
|
|
||||||
|
|
||||||
# Compare binary sizes for all implementations
|
# Compare binary sizes for all implementations
|
||||||
size-comparison:
|
size-comparison:
|
||||||
@echo "=== Binary Size Comparison ==="
|
@mise run size-comparison
|
||||||
@echo "| Implementation | Go (KB) | Go Opt (KB) | TinyGo (KB) | TinyGo Opt (KB) | Javy (KB) | Javy Opt (KB) | Best Reduction |"
|
|
||||||
@echo "|---------------|---------|-------------|-------------|-----------------|-----------|---------------|----------------|"
|
|
||||||
@for impl in $$(ls implementations/); do \
|
|
||||||
if [ -f "implementations/$$impl/main.go" ]; then \
|
|
||||||
echo -n "| $$impl | "; \
|
|
||||||
$(MAKE) build-go IMPL=$$impl > /dev/null 2>&1; \
|
|
||||||
go_size=$$(du -k assets/wasm/lib.wasm.gz | cut -f1); \
|
|
||||||
echo -n "$$go_size | "; \
|
|
||||||
$(MAKE) build-go-optimized IMPL=$$impl > /dev/null 2>&1; \
|
|
||||||
go_opt_size=$$(du -k assets/wasm/lib.wasm.gz | cut -f1); \
|
|
||||||
echo -n "$$go_opt_size | "; \
|
|
||||||
if [ "$$impl" = "goja" ]; then \
|
|
||||||
echo -n "N/A* | N/A* | N/A | N/A | "; \
|
|
||||||
reduction=$$(echo "scale=1; ($$go_size - $$go_opt_size) * 100 / $$go_size" | bc -l 2>/dev/null || echo "N/A"); \
|
|
||||||
echo "$$reduction% |"; \
|
|
||||||
else \
|
|
||||||
$(MAKE) build-tinygo IMPL=$$impl > /dev/null 2>&1; \
|
|
||||||
tinygo_size=$$(du -k assets/wasm/lib.wasm.gz | cut -f1); \
|
|
||||||
echo -n "$$tinygo_size | "; \
|
|
||||||
$(MAKE) build-tinygo-optimized IMPL=$$impl > /dev/null 2>&1; \
|
|
||||||
tinygo_opt_size=$$(du -k assets/wasm/lib.wasm.gz | cut -f1); \
|
|
||||||
echo -n "$$tinygo_opt_size | N/A | N/A | "; \
|
|
||||||
reduction=$$(echo "scale=1; ($$go_size - $$tinygo_opt_size) * 100 / $$go_size" | bc -l 2>/dev/null || echo "N/A"); \
|
|
||||||
echo "$$reduction% |"; \
|
|
||||||
fi \
|
|
||||||
elif [ -f "implementations/$$impl/transform.js" ]; then \
|
|
||||||
echo -n "| $$impl | N/A | N/A | N/A | N/A | "; \
|
|
||||||
$(MAKE) build-javy IMPL=$$impl > /dev/null 2>&1; \
|
|
||||||
javy_size=$$(du -k assets/wasm/lib.wasm.gz | cut -f1); \
|
|
||||||
echo -n "$$javy_size | "; \
|
|
||||||
$(MAKE) build-javy-optimized IMPL=$$impl > /dev/null 2>&1; \
|
|
||||||
javy_opt_size=$$(du -k assets/wasm/lib.wasm.gz | cut -f1); \
|
|
||||||
echo -n "$$javy_opt_size | "; \
|
|
||||||
reduction=$$(echo "scale=1; ($$javy_size - $$javy_opt_size) * 100 / $$javy_size" | bc -l 2>/dev/null || echo "N/A"); \
|
|
||||||
echo "$$reduction% |"; \
|
|
||||||
fi \
|
|
||||||
done
|
|
||||||
@echo ""
|
|
||||||
@echo "*Goja implementation doesn't compile with TinyGo due to dependency complexity"
|
|
||||||
gzipped-sizes:
|
gzipped-sizes:
|
||||||
@echo "<22><> Measuring gzipped sizes of all WASM binaries..."
|
@mise run gzipped-sizes
|
||||||
@node measure-gzipped-sizes.js
|
|
||||||
|
|
||||||
# Build WASM binary with QuickJS (Rust + QuickJS JavaScript engine)
|
# Build WASM binary with QuickJS (Rust + QuickJS JavaScript engine)
|
||||||
build-quickjs:
|
build-quickjs:
|
||||||
@if [ "$(IMPL)" != "quickjs" ]; then \
|
@mise run build-quickjs $(ARGS)
|
||||||
echo "❌ Error: QuickJS build only supports IMPL=quickjs"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "Building WASM binary with QuickJS ($(IMPL) implementation)..."
|
|
||||||
@mkdir -p assets/wasm
|
|
||||||
@echo "Compiling Rust + QuickJS to WASM with WASI target..."
|
|
||||||
@if ! command -v cargo >/dev/null 2>&1; then \
|
|
||||||
echo "❌ Error: Rust/Cargo is not installed. Please install it from https://rustup.rs/"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@cd implementations/$(IMPL) && cargo build --target wasm32-wasip1 --release
|
|
||||||
@echo "Raw size: $$(du -h implementations/$(IMPL)/target/wasm32-wasip1/release/quickjs_transform.wasm | cut -f1)"
|
|
||||||
@echo "Compressing WASM binary..."
|
|
||||||
@gzip -c implementations/$(IMPL)/target/wasm32-wasip1/release/quickjs_transform.wasm > assets/wasm/lib.wasm.gz
|
|
||||||
@echo "✅ QuickJS build complete ($(IMPL)): assets/wasm/lib.wasm.gz ($$(du -h assets/wasm/lib.wasm.gz | cut -f1))"
|
|
||||||
|
|
||||||
# Build WASM binary with QuickJS optimization (already optimized with --release)
|
# Build WASM binary with QuickJS optimization (already optimized with --release)
|
||||||
build-quickjs-optimized:
|
build-quickjs-optimized:
|
||||||
@if [ "$(IMPL)" != "quickjs" ]; then \
|
@mise run build-quickjs-optimized $(ARGS)
|
||||||
echo "❌ Error: QuickJS build only supports IMPL=quickjs"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "Building WASM binary with optimized QuickJS ($(IMPL) implementation)..."
|
|
||||||
@echo "Note: QuickJS Rust build is already optimized with --release profile"
|
|
||||||
@$(MAKE) build-quickjs IMPL=$(IMPL)
|
|
||||||
@echo "✅ Optimized QuickJS build complete ($(IMPL)): assets/wasm/lib.wasm.gz ($$(du -h assets/wasm/lib.wasm.gz | cut -f1))"
|
|
||||||
|
|
||||||
# Test QuickJS implementation directly
|
# Test QuickJS implementation directly
|
||||||
test-quickjs:
|
test-quickjs:
|
||||||
@$(MAKE) build-quickjs IMPL=quickjs > /dev/null 2>&1
|
@mise run test-quickjs $(ARGS)
|
||||||
@echo "Testing QuickJS implementation..."
|
|
||||||
@node implementations/quickjs/quickjs-wasi-test.js 2>/dev/null
|
|
||||||
|
|
||||||
# Test WASI-compatible implementations with Wasmer
|
# Test WASI-compatible implementations with Wasmer
|
||||||
test-wasmer:
|
test-wasmer:
|
||||||
@echo "🧪 Testing WASI-compatible implementations with Wasmer..."
|
@mise run test-wasmer
|
||||||
@./test-wasmer.sh
|
|
||||||
|
|
||||||
# Test individual implementations with Wasmer
|
# Test individual implementations with Wasmer
|
||||||
test-quickjs-wasmer:
|
test-quickjs-wasmer:
|
||||||
@$(MAKE) build-quickjs IMPL=quickjs > /dev/null 2>&1
|
@mise run test-quickjs-wasmer
|
||||||
@echo "Testing QuickJS with Wasmer..."
|
|
||||||
@cd implementations/quickjs && ./quickjs-wasmer-test.sh
|
|
||||||
|
|
||||||
test-porffor-wasmer:
|
test-porffor-wasmer:
|
||||||
@$(MAKE) build-porffor IMPL=porffor > /dev/null 2>&1
|
@mise run test-porffor-wasmer
|
||||||
@echo "Testing Porffor with Wasmer..."
|
|
||||||
@cd implementations/porffor && ./porffor-wasmer-test.sh
|
|
||||||
|
|
||||||
measure-all: size-comparison gzipped-sizes
|
measure-all:
|
||||||
@echo ""
|
@mise run measure-all
|
||||||
@echo "✅ Complete size analysis finished!"
|
|
||||||
|
|||||||
@@ -1,53 +1,26 @@
|
|||||||
// Copyright 2018 The Go Authors. All rights reserved.
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
|
||||||
// This file has been modified for use by the TinyGo compiler.
|
"use strict";
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
// Map multiple JavaScript environments to a single common API,
|
|
||||||
// preferring web standards over Node.js API.
|
|
||||||
//
|
|
||||||
// Environments considered:
|
|
||||||
// - Browsers
|
|
||||||
// - Node.js
|
|
||||||
// - Electron
|
|
||||||
// - Parcel
|
|
||||||
|
|
||||||
if (typeof global !== "undefined") {
|
|
||||||
// global already exists
|
|
||||||
} else if (typeof window !== "undefined") {
|
|
||||||
window.global = window;
|
|
||||||
} else if (typeof self !== "undefined") {
|
|
||||||
self.global = self;
|
|
||||||
} else {
|
|
||||||
throw new Error("cannot export Go (neither global, window nor self is defined)");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!global.require && typeof require !== "undefined") {
|
|
||||||
global.require = require;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!global.fs && global.require) {
|
|
||||||
global.fs = require("node:fs");
|
|
||||||
}
|
|
||||||
|
|
||||||
const enosys = () => {
|
const enosys = () => {
|
||||||
const err = new Error("not implemented");
|
const err = new Error("not implemented");
|
||||||
err.code = "ENOSYS";
|
err.code = "ENOSYS";
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!global.fs) {
|
if (!globalThis.fs) {
|
||||||
let outputBuf = "";
|
let outputBuf = "";
|
||||||
global.fs = {
|
globalThis.fs = {
|
||||||
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
|
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused
|
||||||
writeSync(fd, buf) {
|
writeSync(fd, buf) {
|
||||||
outputBuf += decoder.decode(buf);
|
outputBuf += decoder.decode(buf);
|
||||||
const nl = outputBuf.lastIndexOf("\n");
|
const nl = outputBuf.lastIndexOf("\n");
|
||||||
if (nl != -1) {
|
if (nl != -1) {
|
||||||
console.log(outputBuf.substr(0, nl));
|
console.log(outputBuf.substring(0, nl));
|
||||||
outputBuf = outputBuf.substr(nl + 1);
|
outputBuf = outputBuf.substring(nl + 1);
|
||||||
}
|
}
|
||||||
return buf.length;
|
return buf.length;
|
||||||
},
|
},
|
||||||
@@ -85,8 +58,8 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!global.process) {
|
if (!globalThis.process) {
|
||||||
global.process = {
|
globalThis.process = {
|
||||||
getuid() { return -1; },
|
getuid() { return -1; },
|
||||||
getgid() { return -1; },
|
getgid() { return -1; },
|
||||||
geteuid() { return -1; },
|
geteuid() { return -1; },
|
||||||
@@ -100,53 +73,66 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!global.crypto) {
|
if (!globalThis.path) {
|
||||||
const nodeCrypto = require("node:crypto");
|
globalThis.path = {
|
||||||
global.crypto = {
|
resolve(...pathSegments) {
|
||||||
getRandomValues(b) {
|
return pathSegments.join("/");
|
||||||
nodeCrypto.randomFillSync(b);
|
}
|
||||||
},
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!global.performance) {
|
if (!globalThis.crypto) {
|
||||||
global.performance = {
|
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
|
||||||
now() {
|
|
||||||
const [sec, nsec] = process.hrtime();
|
|
||||||
return sec * 1000 + nsec / 1000000;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!global.TextEncoder) {
|
if (!globalThis.performance) {
|
||||||
global.TextEncoder = require("node:util").TextEncoder;
|
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!global.TextDecoder) {
|
if (!globalThis.TextEncoder) {
|
||||||
global.TextDecoder = require("node:util").TextDecoder;
|
throw new Error("globalThis.TextEncoder is not available, polyfill required");
|
||||||
}
|
}
|
||||||
|
|
||||||
// End of polyfills for common API.
|
if (!globalThis.TextDecoder) {
|
||||||
|
throw new Error("globalThis.TextDecoder is not available, polyfill required");
|
||||||
|
}
|
||||||
|
|
||||||
const encoder = new TextEncoder("utf-8");
|
const encoder = new TextEncoder("utf-8");
|
||||||
const decoder = new TextDecoder("utf-8");
|
const decoder = new TextDecoder("utf-8");
|
||||||
let reinterpretBuf = new DataView(new ArrayBuffer(8));
|
|
||||||
var logLine = [];
|
|
||||||
const wasmExit = {}; // thrown to exit via proc_exit (not an error)
|
|
||||||
|
|
||||||
global.Go = class {
|
globalThis.Go = class {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._callbackTimeouts = new Map();
|
this.argv = ["js"];
|
||||||
|
this.env = {};
|
||||||
|
this.exit = (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
console.warn("exit code:", code);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._exitPromise = new Promise((resolve) => {
|
||||||
|
this._resolveExitPromise = resolve;
|
||||||
|
});
|
||||||
|
this._pendingEvent = null;
|
||||||
|
this._scheduledTimeouts = new Map();
|
||||||
this._nextCallbackTimeoutID = 1;
|
this._nextCallbackTimeoutID = 1;
|
||||||
|
|
||||||
const mem = () => {
|
const setInt64 = (addr, v) => {
|
||||||
// The buffer may change when requesting more memory.
|
this.mem.setUint32(addr + 0, v, true);
|
||||||
return new DataView(this._inst.exports.memory.buffer);
|
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const unboxValue = (v_ref) => {
|
const setInt32 = (addr, v) => {
|
||||||
reinterpretBuf.setBigInt64(0, v_ref, true);
|
this.mem.setUint32(addr + 0, v, true);
|
||||||
const f = reinterpretBuf.getFloat64(0, true);
|
}
|
||||||
|
|
||||||
|
const getInt64 = (addr) => {
|
||||||
|
const low = this.mem.getUint32(addr + 0, true);
|
||||||
|
const high = this.mem.getInt32(addr + 4, true);
|
||||||
|
return low + high * 4294967296;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadValue = (addr) => {
|
||||||
|
const f = this.mem.getFloat64(addr, true);
|
||||||
if (f === 0) {
|
if (f === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -154,77 +140,69 @@
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = v_ref & 0xffffffffn;
|
const id = this.mem.getUint32(addr, true);
|
||||||
return this._values[id];
|
return this._values[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const storeValue = (addr, v) => {
|
||||||
|
const nanHead = 0x7FF80000;
|
||||||
|
|
||||||
const loadValue = (addr) => {
|
if (typeof v === "number" && v !== 0) {
|
||||||
let v_ref = mem().getBigUint64(addr, true);
|
|
||||||
return unboxValue(v_ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
const boxValue = (v) => {
|
|
||||||
const nanHead = 0x7FF80000n;
|
|
||||||
|
|
||||||
if (typeof v === "number") {
|
|
||||||
if (isNaN(v)) {
|
if (isNaN(v)) {
|
||||||
return nanHead << 32n;
|
this.mem.setUint32(addr + 4, nanHead, true);
|
||||||
|
this.mem.setUint32(addr, 0, true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (v === 0) {
|
this.mem.setFloat64(addr, v, true);
|
||||||
return (nanHead << 32n) | 1n;
|
return;
|
||||||
}
|
|
||||||
reinterpretBuf.setFloat64(0, v, true);
|
|
||||||
return reinterpretBuf.getBigInt64(0, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (v) {
|
if (v === undefined) {
|
||||||
case undefined:
|
this.mem.setFloat64(addr, 0, true);
|
||||||
return 0n;
|
return;
|
||||||
case null:
|
|
||||||
return (nanHead << 32n) | 2n;
|
|
||||||
case true:
|
|
||||||
return (nanHead << 32n) | 3n;
|
|
||||||
case false:
|
|
||||||
return (nanHead << 32n) | 4n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = this._ids.get(v);
|
let id = this._ids.get(v);
|
||||||
if (id === undefined) {
|
if (id === undefined) {
|
||||||
id = this._idPool.pop();
|
id = this._idPool.pop();
|
||||||
if (id === undefined) {
|
if (id === undefined) {
|
||||||
id = BigInt(this._values.length);
|
id = this._values.length;
|
||||||
}
|
}
|
||||||
this._values[id] = v;
|
this._values[id] = v;
|
||||||
this._goRefCounts[id] = 0;
|
this._goRefCounts[id] = 0;
|
||||||
this._ids.set(v, id);
|
this._ids.set(v, id);
|
||||||
}
|
}
|
||||||
this._goRefCounts[id]++;
|
this._goRefCounts[id]++;
|
||||||
let typeFlag = 1n;
|
let typeFlag = 0;
|
||||||
switch (typeof v) {
|
switch (typeof v) {
|
||||||
|
case "object":
|
||||||
|
if (v !== null) {
|
||||||
|
typeFlag = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "string":
|
case "string":
|
||||||
typeFlag = 2n;
|
typeFlag = 2;
|
||||||
break;
|
break;
|
||||||
case "symbol":
|
case "symbol":
|
||||||
typeFlag = 3n;
|
typeFlag = 3;
|
||||||
break;
|
break;
|
||||||
case "function":
|
case "function":
|
||||||
typeFlag = 4n;
|
typeFlag = 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return id | ((nanHead | typeFlag) << 32n);
|
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
||||||
|
this.mem.setUint32(addr, id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const storeValue = (addr, v) => {
|
const loadSlice = (addr) => {
|
||||||
let v_ref = boxValue(v);
|
const array = getInt64(addr + 0);
|
||||||
mem().setBigUint64(addr, v_ref, true);
|
const len = getInt64(addr + 8);
|
||||||
|
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadSlice = (array, len, cap) => {
|
const loadSliceOfValues = (addr) => {
|
||||||
return new Uint8Array(this._inst.exports.memory.buffer, array, len);
|
const array = getInt64(addr + 0);
|
||||||
}
|
const len = getInt64(addr + 8);
|
||||||
|
|
||||||
const loadSliceOfValues = (array, len, cap) => {
|
|
||||||
const a = new Array(len);
|
const a = new Array(len);
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
a[i] = loadValue(array + i * 8);
|
a[i] = loadValue(array + i * 8);
|
||||||
@@ -232,81 +210,109 @@
|
|||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadString = (ptr, len) => {
|
const loadString = (addr) => {
|
||||||
return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len));
|
const saddr = getInt64(addr + 0);
|
||||||
|
const len = getInt64(addr + 8);
|
||||||
|
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
const testCallExport = (a, b) => {
|
||||||
|
this._inst.exports.testExport0();
|
||||||
|
return this._inst.exports.testExport(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeOrigin = Date.now() - performance.now();
|
const timeOrigin = Date.now() - performance.now();
|
||||||
this.importObject = {
|
this.importObject = {
|
||||||
wasi_snapshot_preview1: {
|
_gotest: {
|
||||||
// https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write
|
add: (a, b) => a + b,
|
||||||
fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) {
|
callExport: testCallExport,
|
||||||
let nwritten = 0;
|
|
||||||
if (fd == 1) {
|
|
||||||
for (let iovs_i=0; iovs_i<iovs_len;iovs_i++) {
|
|
||||||
let iov_ptr = iovs_ptr+iovs_i*8; // assuming wasm32
|
|
||||||
let ptr = mem().getUint32(iov_ptr + 0, true);
|
|
||||||
let len = mem().getUint32(iov_ptr + 4, true);
|
|
||||||
nwritten += len;
|
|
||||||
for (let i=0; i<len; i++) {
|
|
||||||
let c = mem().getUint8(ptr+i);
|
|
||||||
if (c == 13) { // CR
|
|
||||||
// ignore
|
|
||||||
} else if (c == 10) { // LF
|
|
||||||
// write line
|
|
||||||
let line = decoder.decode(new Uint8Array(logLine));
|
|
||||||
logLine = [];
|
|
||||||
console.log(line);
|
|
||||||
} else {
|
|
||||||
logLine.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('invalid file descriptor:', fd);
|
|
||||||
}
|
|
||||||
mem().setUint32(nwritten_ptr, nwritten, true);
|
|
||||||
return 0;
|
|
||||||
},
|
|
||||||
fd_close: () => 0, // dummy
|
|
||||||
fd_fdstat_get: () => 0, // dummy
|
|
||||||
fd_seek: () => 0, // dummy
|
|
||||||
proc_exit: (code) => {
|
|
||||||
this.exited = true;
|
|
||||||
this.exitCode = code;
|
|
||||||
this._resolveExitPromise();
|
|
||||||
throw wasmExit;
|
|
||||||
},
|
|
||||||
random_get: (bufPtr, bufLen) => {
|
|
||||||
crypto.getRandomValues(loadSlice(bufPtr, bufLen));
|
|
||||||
return 0;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
gojs: {
|
gojs: {
|
||||||
// func ticks() float64
|
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
||||||
"runtime.ticks": () => {
|
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
||||||
return timeOrigin + performance.now();
|
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
||||||
|
// This changes the SP, thus we have to update the SP used by the imported function.
|
||||||
|
|
||||||
|
// func wasmExit(code int32)
|
||||||
|
"runtime.wasmExit": (sp) => {
|
||||||
|
sp >>>= 0;
|
||||||
|
const code = this.mem.getInt32(sp + 8, true);
|
||||||
|
this.exited = true;
|
||||||
|
delete this._inst;
|
||||||
|
delete this._values;
|
||||||
|
delete this._goRefCounts;
|
||||||
|
delete this._ids;
|
||||||
|
delete this._idPool;
|
||||||
|
this.exit(code);
|
||||||
},
|
},
|
||||||
|
|
||||||
// func sleepTicks(timeout float64)
|
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
||||||
"runtime.sleepTicks": (timeout) => {
|
"runtime.wasmWrite": (sp) => {
|
||||||
// Do not sleep, only reactivate scheduler after the given timeout.
|
sp >>>= 0;
|
||||||
setTimeout(() => {
|
const fd = getInt64(sp + 8);
|
||||||
if (this.exited) return;
|
const p = getInt64(sp + 16);
|
||||||
try {
|
const n = this.mem.getInt32(sp + 24, true);
|
||||||
this._inst.exports.go_scheduler();
|
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
|
||||||
} catch (e) {
|
},
|
||||||
if (e !== wasmExit) throw e;
|
|
||||||
|
// func resetMemoryDataView()
|
||||||
|
"runtime.resetMemoryDataView": (sp) => {
|
||||||
|
sp >>>= 0;
|
||||||
|
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||||
|
},
|
||||||
|
|
||||||
|
// func nanotime1() int64
|
||||||
|
"runtime.nanotime1": (sp) => {
|
||||||
|
sp >>>= 0;
|
||||||
|
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
||||||
|
},
|
||||||
|
|
||||||
|
// func walltime() (sec int64, nsec int32)
|
||||||
|
"runtime.walltime": (sp) => {
|
||||||
|
sp >>>= 0;
|
||||||
|
const msec = (new Date).getTime();
|
||||||
|
setInt64(sp + 8, msec / 1000);
|
||||||
|
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
// func scheduleTimeoutEvent(delay int64) int32
|
||||||
|
"runtime.scheduleTimeoutEvent": (sp) => {
|
||||||
|
sp >>>= 0;
|
||||||
|
const id = this._nextCallbackTimeoutID;
|
||||||
|
this._nextCallbackTimeoutID++;
|
||||||
|
this._scheduledTimeouts.set(id, setTimeout(
|
||||||
|
() => {
|
||||||
|
this._resume();
|
||||||
|
while (this._scheduledTimeouts.has(id)) {
|
||||||
|
// for some reason Go failed to register the timeout event, log and try again
|
||||||
|
// (temporary workaround for https://github.com/golang/go/issues/28975)
|
||||||
|
console.warn("scheduleTimeoutEvent: missed timeout event");
|
||||||
|
this._resume();
|
||||||
}
|
}
|
||||||
}, timeout);
|
},
|
||||||
|
getInt64(sp + 8),
|
||||||
|
));
|
||||||
|
this.mem.setInt32(sp + 16, id, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
// func clearTimeoutEvent(id int32)
|
||||||
|
"runtime.clearTimeoutEvent": (sp) => {
|
||||||
|
sp >>>= 0;
|
||||||
|
const id = this.mem.getInt32(sp + 8, true);
|
||||||
|
clearTimeout(this._scheduledTimeouts.get(id));
|
||||||
|
this._scheduledTimeouts.delete(id);
|
||||||
|
},
|
||||||
|
|
||||||
|
// func getRandomData(r []byte)
|
||||||
|
"runtime.getRandomData": (sp) => {
|
||||||
|
sp >>>= 0;
|
||||||
|
crypto.getRandomValues(loadSlice(sp + 8));
|
||||||
},
|
},
|
||||||
|
|
||||||
// func finalizeRef(v ref)
|
// func finalizeRef(v ref)
|
||||||
"syscall/js.finalizeRef": (v_ref) => {
|
"syscall/js.finalizeRef": (sp) => {
|
||||||
// Note: TinyGo does not support finalizers so this is only called
|
sp >>>= 0;
|
||||||
// for one specific case, by js.go:jsString. and can/might leak memory.
|
const id = this.mem.getUint32(sp + 8, true);
|
||||||
const id = v_ref & 0xffffffffn;
|
|
||||||
if (this._goRefCounts?.[id] !== undefined) {
|
|
||||||
this._goRefCounts[id]--;
|
this._goRefCounts[id]--;
|
||||||
if (this._goRefCounts[id] === 0) {
|
if (this._goRefCounts[id] === 0) {
|
||||||
const v = this._values[id];
|
const v = this._values[id];
|
||||||
@@ -314,205 +320,243 @@
|
|||||||
this._ids.delete(v);
|
this._ids.delete(v);
|
||||||
this._idPool.push(id);
|
this._idPool.push(id);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.error("syscall/js.finalizeRef: unknown id", id);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// func stringVal(value string) ref
|
// func stringVal(value string) ref
|
||||||
"syscall/js.stringVal": (value_ptr, value_len) => {
|
"syscall/js.stringVal": (sp) => {
|
||||||
value_ptr >>>= 0;
|
sp >>>= 0;
|
||||||
const s = loadString(value_ptr, value_len);
|
storeValue(sp + 24, loadString(sp + 8));
|
||||||
return boxValue(s);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// func valueGet(v ref, p string) ref
|
// func valueGet(v ref, p string) ref
|
||||||
"syscall/js.valueGet": (v_ref, p_ptr, p_len) => {
|
"syscall/js.valueGet": (sp) => {
|
||||||
let prop = loadString(p_ptr, p_len);
|
sp >>>= 0;
|
||||||
let v = unboxValue(v_ref);
|
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
|
||||||
let result = Reflect.get(v, prop);
|
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||||
return boxValue(result);
|
storeValue(sp + 32, result);
|
||||||
},
|
},
|
||||||
|
|
||||||
// func valueSet(v ref, p string, x ref)
|
// func valueSet(v ref, p string, x ref)
|
||||||
"syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => {
|
"syscall/js.valueSet": (sp) => {
|
||||||
const v = unboxValue(v_ref);
|
sp >>>= 0;
|
||||||
const p = loadString(p_ptr, p_len);
|
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
|
||||||
const x = unboxValue(x_ref);
|
|
||||||
Reflect.set(v, p, x);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// func valueDelete(v ref, p string)
|
// func valueDelete(v ref, p string)
|
||||||
"syscall/js.valueDelete": (v_ref, p_ptr, p_len) => {
|
"syscall/js.valueDelete": (sp) => {
|
||||||
const v = unboxValue(v_ref);
|
sp >>>= 0;
|
||||||
const p = loadString(p_ptr, p_len);
|
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
|
||||||
Reflect.deleteProperty(v, p);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// func valueIndex(v ref, i int) ref
|
// func valueIndex(v ref, i int) ref
|
||||||
"syscall/js.valueIndex": (v_ref, i) => {
|
"syscall/js.valueIndex": (sp) => {
|
||||||
return boxValue(Reflect.get(unboxValue(v_ref), i));
|
sp >>>= 0;
|
||||||
|
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
|
||||||
},
|
},
|
||||||
|
|
||||||
// valueSetIndex(v ref, i int, x ref)
|
// valueSetIndex(v ref, i int, x ref)
|
||||||
"syscall/js.valueSetIndex": (v_ref, i, x_ref) => {
|
"syscall/js.valueSetIndex": (sp) => {
|
||||||
Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref));
|
sp >>>= 0;
|
||||||
|
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
|
||||||
},
|
},
|
||||||
|
|
||||||
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||||
"syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => {
|
"syscall/js.valueCall": (sp) => {
|
||||||
const v = unboxValue(v_ref);
|
sp >>>= 0;
|
||||||
const name = loadString(m_ptr, m_len);
|
|
||||||
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
|
||||||
try {
|
try {
|
||||||
const m = Reflect.get(v, name);
|
const v = loadValue(sp + 8);
|
||||||
storeValue(ret_addr, Reflect.apply(m, v, args));
|
const m = Reflect.get(v, loadString(sp + 16));
|
||||||
mem().setUint8(ret_addr + 8, 1);
|
const args = loadSliceOfValues(sp + 32);
|
||||||
|
const result = Reflect.apply(m, v, args);
|
||||||
|
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||||
|
storeValue(sp + 56, result);
|
||||||
|
this.mem.setUint8(sp + 64, 1);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
storeValue(ret_addr, err);
|
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||||
mem().setUint8(ret_addr + 8, 0);
|
storeValue(sp + 56, err);
|
||||||
|
this.mem.setUint8(sp + 64, 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// func valueInvoke(v ref, args []ref) (ref, bool)
|
// func valueInvoke(v ref, args []ref) (ref, bool)
|
||||||
"syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
|
"syscall/js.valueInvoke": (sp) => {
|
||||||
|
sp >>>= 0;
|
||||||
try {
|
try {
|
||||||
const v = unboxValue(v_ref);
|
const v = loadValue(sp + 8);
|
||||||
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
const args = loadSliceOfValues(sp + 16);
|
||||||
storeValue(ret_addr, Reflect.apply(v, undefined, args));
|
const result = Reflect.apply(v, undefined, args);
|
||||||
mem().setUint8(ret_addr + 8, 1);
|
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||||
|
storeValue(sp + 40, result);
|
||||||
|
this.mem.setUint8(sp + 48, 1);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
storeValue(ret_addr, err);
|
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||||
mem().setUint8(ret_addr + 8, 0);
|
storeValue(sp + 40, err);
|
||||||
|
this.mem.setUint8(sp + 48, 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// func valueNew(v ref, args []ref) (ref, bool)
|
// func valueNew(v ref, args []ref) (ref, bool)
|
||||||
"syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
|
"syscall/js.valueNew": (sp) => {
|
||||||
const v = unboxValue(v_ref);
|
sp >>>= 0;
|
||||||
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
|
||||||
try {
|
try {
|
||||||
storeValue(ret_addr, Reflect.construct(v, args));
|
const v = loadValue(sp + 8);
|
||||||
mem().setUint8(ret_addr + 8, 1);
|
const args = loadSliceOfValues(sp + 16);
|
||||||
|
const result = Reflect.construct(v, args);
|
||||||
|
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||||
|
storeValue(sp + 40, result);
|
||||||
|
this.mem.setUint8(sp + 48, 1);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
storeValue(ret_addr, err);
|
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||||
mem().setUint8(ret_addr+ 8, 0);
|
storeValue(sp + 40, err);
|
||||||
|
this.mem.setUint8(sp + 48, 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// func valueLength(v ref) int
|
// func valueLength(v ref) int
|
||||||
"syscall/js.valueLength": (v_ref) => {
|
"syscall/js.valueLength": (sp) => {
|
||||||
return unboxValue(v_ref).length;
|
sp >>>= 0;
|
||||||
|
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
||||||
},
|
},
|
||||||
|
|
||||||
// valuePrepareString(v ref) (ref, int)
|
// valuePrepareString(v ref) (ref, int)
|
||||||
"syscall/js.valuePrepareString": (ret_addr, v_ref) => {
|
"syscall/js.valuePrepareString": (sp) => {
|
||||||
const s = String(unboxValue(v_ref));
|
sp >>>= 0;
|
||||||
const str = encoder.encode(s);
|
const str = encoder.encode(String(loadValue(sp + 8)));
|
||||||
storeValue(ret_addr, str);
|
storeValue(sp + 16, str);
|
||||||
mem().setInt32(ret_addr + 8, str.length, true);
|
setInt64(sp + 24, str.length);
|
||||||
},
|
},
|
||||||
|
|
||||||
// valueLoadString(v ref, b []byte)
|
// valueLoadString(v ref, b []byte)
|
||||||
"syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => {
|
"syscall/js.valueLoadString": (sp) => {
|
||||||
const str = unboxValue(v_ref);
|
sp >>>= 0;
|
||||||
loadSlice(slice_ptr, slice_len, slice_cap).set(str);
|
const str = loadValue(sp + 8);
|
||||||
|
loadSlice(sp + 16).set(str);
|
||||||
},
|
},
|
||||||
|
|
||||||
// func valueInstanceOf(v ref, t ref) bool
|
// func valueInstanceOf(v ref, t ref) bool
|
||||||
"syscall/js.valueInstanceOf": (v_ref, t_ref) => {
|
"syscall/js.valueInstanceOf": (sp) => {
|
||||||
return unboxValue(v_ref) instanceof unboxValue(t_ref);
|
sp >>>= 0;
|
||||||
|
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||||
"syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => {
|
"syscall/js.copyBytesToGo": (sp) => {
|
||||||
let num_bytes_copied_addr = ret_addr;
|
sp >>>= 0;
|
||||||
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
|
const dst = loadSlice(sp + 8);
|
||||||
|
const src = loadValue(sp + 32);
|
||||||
const dst = loadSlice(dest_addr, dest_len);
|
|
||||||
const src = unboxValue(src_ref);
|
|
||||||
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
||||||
mem().setUint8(returned_status_addr, 0); // Return "not ok" status
|
this.mem.setUint8(sp + 48, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const toCopy = src.subarray(0, dst.length);
|
const toCopy = src.subarray(0, dst.length);
|
||||||
dst.set(toCopy);
|
dst.set(toCopy);
|
||||||
mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
|
setInt64(sp + 40, toCopy.length);
|
||||||
mem().setUint8(returned_status_addr, 1); // Return "ok" status
|
this.mem.setUint8(sp + 48, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
// copyBytesToJS(dst ref, src []byte) (int, bool)
|
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
||||||
// Originally copied from upstream Go project, then modified:
|
"syscall/js.copyBytesToJS": (sp) => {
|
||||||
// https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416
|
sp >>>= 0;
|
||||||
"syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => {
|
const dst = loadValue(sp + 8);
|
||||||
let num_bytes_copied_addr = ret_addr;
|
const src = loadSlice(sp + 16);
|
||||||
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
|
|
||||||
|
|
||||||
const dst = unboxValue(dst_ref);
|
|
||||||
const src = loadSlice(src_addr, src_len);
|
|
||||||
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
||||||
mem().setUint8(returned_status_addr, 0); // Return "not ok" status
|
this.mem.setUint8(sp + 48, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const toCopy = src.subarray(0, dst.length);
|
const toCopy = src.subarray(0, dst.length);
|
||||||
dst.set(toCopy);
|
dst.set(toCopy);
|
||||||
mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
|
setInt64(sp + 40, toCopy.length);
|
||||||
mem().setUint8(returned_status_addr, 1); // Return "ok" status
|
this.mem.setUint8(sp + 48, 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
"debug": (value) => {
|
||||||
|
console.log(value);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Go 1.20 uses 'env'. Go 1.21 uses 'gojs'.
|
|
||||||
// For compatibility, we use both as long as Go 1.20 is supported.
|
|
||||||
this.importObject.env = this.importObject.gojs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(instance) {
|
async run(instance) {
|
||||||
|
if (!(instance instanceof WebAssembly.Instance)) {
|
||||||
|
throw new Error("Go.run: WebAssembly.Instance expected");
|
||||||
|
}
|
||||||
this._inst = instance;
|
this._inst = instance;
|
||||||
|
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||||
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
||||||
NaN,
|
NaN,
|
||||||
0,
|
0,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
global,
|
globalThis,
|
||||||
this,
|
this,
|
||||||
];
|
];
|
||||||
this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id
|
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
|
||||||
this._ids = new Map(); // mapping from JS values to reference ids
|
this._ids = new Map([ // mapping from JS values to reference ids
|
||||||
|
[0, 1],
|
||||||
|
[null, 2],
|
||||||
|
[true, 3],
|
||||||
|
[false, 4],
|
||||||
|
[globalThis, 5],
|
||||||
|
[this, 6],
|
||||||
|
]);
|
||||||
this._idPool = []; // unused ids that have been garbage collected
|
this._idPool = []; // unused ids that have been garbage collected
|
||||||
this.exited = false; // whether the Go program has exited
|
this.exited = false; // whether the Go program has exited
|
||||||
this.exitCode = 0;
|
|
||||||
|
|
||||||
if (this._inst.exports._start) {
|
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
||||||
let exitPromise = new Promise((resolve, reject) => {
|
let offset = 4096;
|
||||||
this._resolveExitPromise = resolve;
|
|
||||||
|
const strPtr = (str) => {
|
||||||
|
const ptr = offset;
|
||||||
|
const bytes = encoder.encode(str + "\0");
|
||||||
|
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
|
||||||
|
offset += bytes.length;
|
||||||
|
if (offset % 8 !== 0) {
|
||||||
|
offset += 8 - (offset % 8);
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const argc = this.argv.length;
|
||||||
|
|
||||||
|
const argvPtrs = [];
|
||||||
|
this.argv.forEach((arg) => {
|
||||||
|
argvPtrs.push(strPtr(arg));
|
||||||
|
});
|
||||||
|
argvPtrs.push(0);
|
||||||
|
|
||||||
|
const keys = Object.keys(this.env).sort();
|
||||||
|
keys.forEach((key) => {
|
||||||
|
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
|
||||||
|
});
|
||||||
|
argvPtrs.push(0);
|
||||||
|
|
||||||
|
const argv = offset;
|
||||||
|
argvPtrs.forEach((ptr) => {
|
||||||
|
this.mem.setUint32(offset, ptr, true);
|
||||||
|
this.mem.setUint32(offset + 4, 0, true);
|
||||||
|
offset += 8;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run program, but catch the wasmExit exception that's thrown
|
// The linker guarantees global data starts from at least wasmMinDataAddr.
|
||||||
// to return back here.
|
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
|
||||||
try {
|
const wasmMinDataAddr = 4096 + 8192;
|
||||||
this._inst.exports._start();
|
if (offset >= wasmMinDataAddr) {
|
||||||
} catch (e) {
|
throw new Error("total length of command line and environment variables exceeds limit");
|
||||||
if (e !== wasmExit) throw e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await exitPromise;
|
this._inst.exports.run(argc, argv);
|
||||||
return this.exitCode;
|
if (this.exited) {
|
||||||
} else {
|
this._resolveExitPromise();
|
||||||
this._inst.exports._initialize();
|
|
||||||
}
|
}
|
||||||
|
await this._exitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
_resume() {
|
_resume() {
|
||||||
if (this.exited) {
|
if (this.exited) {
|
||||||
throw new Error("Go program has already exited");
|
throw new Error("Go program has already exited");
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
this._inst.exports.resume();
|
this._inst.exports.resume();
|
||||||
} catch (e) {
|
|
||||||
if (e !== wasmExit) throw e;
|
|
||||||
}
|
|
||||||
if (this.exited) {
|
if (this.exited) {
|
||||||
this._resolveExitPromise();
|
this._resolveExitPromise();
|
||||||
}
|
}
|
||||||
@@ -528,26 +572,4 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
global.require &&
|
|
||||||
global.require.main === module &&
|
|
||||||
global.process &&
|
|
||||||
global.process.versions &&
|
|
||||||
!global.process.versions.electron
|
|
||||||
) {
|
|
||||||
if (process.argv.length != 3) {
|
|
||||||
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const go = new Go();
|
|
||||||
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => {
|
|
||||||
let exitCode = await go.run(result.instance);
|
|
||||||
process.exit(exitCode);
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
76
implementations/assemblyscript/adapter.js
Normal file
76
implementations/assemblyscript/adapter.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { instantiate } from "@assemblyscript/loader";
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
// Load and instantiate the WASM module
|
||||||
|
export async function loadWasm() {
|
||||||
|
const wasmPath = path.join(__dirname, "build", "release.wasm");
|
||||||
|
const wasmBuffer = await fs.readFile(wasmPath);
|
||||||
|
|
||||||
|
const { exports } = await instantiate(wasmBuffer, {
|
||||||
|
env: {
|
||||||
|
"console.log": (msgPtr) => {
|
||||||
|
console.log(exports.__getString(msgPtr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
memory,
|
||||||
|
__newString,
|
||||||
|
__getString,
|
||||||
|
transformDataWithTimestamp,
|
||||||
|
healthCheck
|
||||||
|
} = exports;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Promise-based API for compatibility with tests
|
||||||
|
async transformData(jsonString) {
|
||||||
|
try {
|
||||||
|
// Generate ISO timestamp in JavaScript
|
||||||
|
const isoTimestamp = new Date().toISOString();
|
||||||
|
|
||||||
|
// Marshal strings to/from WASM
|
||||||
|
const jsonPtr = __newString(jsonString);
|
||||||
|
const timestampPtr = __newString(isoTimestamp);
|
||||||
|
const resultPtr = transformDataWithTimestamp(jsonPtr, timestampPtr);
|
||||||
|
const result = __getString(resultPtr);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
// Match Go behavior on parse errors
|
||||||
|
throw new Error(`failed to parse input JSON: ${error.message}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async healthCheck() {
|
||||||
|
try {
|
||||||
|
const resultPtr = healthCheck();
|
||||||
|
const result = __getString(resultPtr);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`health check failed: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export individual functions for testing
|
||||||
|
let wasmInstance = null;
|
||||||
|
|
||||||
|
export async function transformData(jsonString) {
|
||||||
|
if (!wasmInstance) {
|
||||||
|
wasmInstance = await loadWasm();
|
||||||
|
}
|
||||||
|
return wasmInstance.transformData(jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function healthCheck() {
|
||||||
|
if (!wasmInstance) {
|
||||||
|
wasmInstance = await loadWasm();
|
||||||
|
}
|
||||||
|
return wasmInstance.healthCheck();
|
||||||
|
}
|
||||||
17
implementations/assemblyscript/asconfig.json
Normal file
17
implementations/assemblyscript/asconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"targets": {
|
||||||
|
"release": {
|
||||||
|
"outFile": "build/release.wasm",
|
||||||
|
"optimizeLevel": 3,
|
||||||
|
"shrinkLevel": 2,
|
||||||
|
"converge": true,
|
||||||
|
"noAssert": true,
|
||||||
|
"runtime": "stub",
|
||||||
|
"transform": ["json-as/transform"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"exportRuntime": false,
|
||||||
|
"bindings": "raw"
|
||||||
|
}
|
||||||
|
}
|
||||||
42
implementations/assemblyscript/assembly/index.ts
Normal file
42
implementations/assemblyscript/assembly/index.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { JSON } from "json-as/assembly";
|
||||||
|
|
||||||
|
// Define data structures with @json decorator
|
||||||
|
@json
|
||||||
|
class HealthResponse {
|
||||||
|
status!: string;
|
||||||
|
message!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health check function
|
||||||
|
export function healthCheck(): string {
|
||||||
|
const response = new HealthResponse();
|
||||||
|
response.status = "healthy";
|
||||||
|
response.message = "AssemblyScript WASM module is running";
|
||||||
|
return JSON.stringify<HealthResponse>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform data function with timestamp passed from JS
|
||||||
|
export function transformDataWithTimestamp(jsonStr: string, isoTimestamp: string): string {
|
||||||
|
// Parse arbitrary JSON into a JSON.Value
|
||||||
|
let original: JSON.Value;
|
||||||
|
original = JSON.parse<JSON.Value>(jsonStr);
|
||||||
|
|
||||||
|
// Create wrapper object
|
||||||
|
const wrapper = new JSON.Obj();
|
||||||
|
|
||||||
|
// Populate fields
|
||||||
|
wrapper.set("original", original);
|
||||||
|
wrapper.set("transformed", true);
|
||||||
|
wrapper.set("timestamp", isoTimestamp);
|
||||||
|
wrapper.set("message", "Data has been processed by AssemblyScript WASM");
|
||||||
|
|
||||||
|
// Stringify the complete object
|
||||||
|
return wrapper.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export with the standard name for compatibility
|
||||||
|
export function transformData(jsonStr: string): string {
|
||||||
|
// For backward compatibility, but this shouldn't be used directly
|
||||||
|
// The JS adapter will use transformDataWithTimestamp
|
||||||
|
return transformDataWithTimestamp(jsonStr, "");
|
||||||
|
}
|
||||||
20
implementations/assemblyscript/package.json
Normal file
20
implementations/assemblyscript/package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "assemblyscript-json-transform",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "AssemblyScript JSON transformation using json-as",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"asbuild:release": "asc assembly/index.ts --target release --transform json-as/transform",
|
||||||
|
"asbuild": "npm run asbuild:release",
|
||||||
|
"build": "npm run asbuild && wasm-opt -Oz --strip-debug --strip-dwarf --strip-producers -o build/module.opt.wasm build/release.wasm",
|
||||||
|
"test": "node test.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@assemblyscript/loader": "^0.28.4",
|
||||||
|
"assemblyscript": "^0.28.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"json-as": "^1.1.21"
|
||||||
|
}
|
||||||
|
}
|
||||||
59
implementations/assemblyscript/test.js
Normal file
59
implementations/assemblyscript/test.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { transformData, healthCheck } from './adapter.js';
|
||||||
|
|
||||||
|
async function runTests() {
|
||||||
|
console.log('🧪 Testing AssemblyScript implementation...\n');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Test 1: Health check
|
||||||
|
console.log('Test 1: Health check');
|
||||||
|
const health = await healthCheck();
|
||||||
|
const healthObj = JSON.parse(health);
|
||||||
|
console.log('✅ Health:', healthObj);
|
||||||
|
if (healthObj.status !== 'healthy') {
|
||||||
|
throw new Error('Health check failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Simple object transformation
|
||||||
|
console.log('\nTest 2: Simple object');
|
||||||
|
const simple = await transformData('{"name":"test","value":42}');
|
||||||
|
const simpleObj = JSON.parse(simple);
|
||||||
|
console.log('✅ Simple:', simpleObj);
|
||||||
|
if (simpleObj.original.name !== 'test' || simpleObj.original.value !== 42) {
|
||||||
|
throw new Error('Simple object test failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Array transformation
|
||||||
|
console.log('\nTest 3: Array');
|
||||||
|
const array = await transformData('[1,2,3,"test"]');
|
||||||
|
const arrayObj = JSON.parse(array);
|
||||||
|
console.log('✅ Array:', arrayObj);
|
||||||
|
if (!Array.isArray(arrayObj.original) || arrayObj.original.length !== 4) {
|
||||||
|
throw new Error('Array test failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: Nested object
|
||||||
|
console.log('\nTest 4: Nested object');
|
||||||
|
const nested = await transformData('{"user":{"id":1,"name":"John"},"meta":{"version":"1.0"}}');
|
||||||
|
const nestedObj = JSON.parse(nested);
|
||||||
|
console.log('✅ Nested:', nestedObj);
|
||||||
|
if (nestedObj.original.user.name !== 'John') {
|
||||||
|
throw new Error('Nested object test failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 5: Invalid JSON
|
||||||
|
console.log('\nTest 5: Invalid JSON');
|
||||||
|
try {
|
||||||
|
await transformData('invalid json {');
|
||||||
|
throw new Error('Should have thrown on invalid JSON');
|
||||||
|
} catch (error) {
|
||||||
|
console.log('✅ Invalid JSON correctly rejected:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ All tests passed!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ Test failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests().catch(console.error);
|
||||||
@@ -48,21 +48,36 @@ func promisify(fn func([]js.Value) (string, error)) js.Func {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// transformData takes a JSON string and JavaScript code, processes it using Goja JavaScript engine, and returns a JSON string
|
// transformData takes a JSON string, processes it using Goja JavaScript engine with a default transform, and returns a JSON string
|
||||||
func transformData(args []js.Value) (string, error) {
|
func transformData(args []js.Value) (string, error) {
|
||||||
js.Global().Get("console").Call("log", "🔄 transformData called with", len(args), "arguments")
|
js.Global().Get("console").Call("log", "🔄 transformData called with", len(args), "arguments")
|
||||||
|
|
||||||
if len(args) < 2 {
|
if len(args) < 1 {
|
||||||
return "", fmt.Errorf("expected two arguments: JSON string and JavaScript code")
|
return "", fmt.Errorf("expected at least one argument (JSON string)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the input JSON string
|
// Get the input JSON string
|
||||||
inputJSON := args[0].String()
|
inputJSON := args[0].String()
|
||||||
js.Global().Get("console").Call("log", "📥 Input JSON:", inputJSON)
|
js.Global().Get("console").Call("log", "📥 Input JSON:", inputJSON)
|
||||||
|
|
||||||
// Get the JavaScript transformation code
|
// Use a default transformation if no JavaScript code is provided
|
||||||
transformJS := args[1].String()
|
var transformJS string
|
||||||
|
if len(args) >= 2 {
|
||||||
|
transformJS = args[1].String()
|
||||||
js.Global().Get("console").Call("log", "📜 JavaScript code length:", len(transformJS), "characters")
|
js.Global().Get("console").Call("log", "📜 JavaScript code length:", len(transformJS), "characters")
|
||||||
|
} else {
|
||||||
|
// Default transformation that matches the test expectations
|
||||||
|
transformJS = `
|
||||||
|
function transform(data) {
|
||||||
|
return {
|
||||||
|
original: data,
|
||||||
|
transformed: true,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
message: "Data has been processed by Go WASM"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the input JSON
|
// Parse the input JSON
|
||||||
var inputData interface{}
|
var inputData interface{}
|
||||||
@@ -118,7 +133,7 @@ func main() {
|
|||||||
// Add a simple health check function
|
// Add a simple health check function
|
||||||
js.Global().Set("healthCheck", promisify(func(args []js.Value) (string, error) {
|
js.Global().Set("healthCheck", promisify(func(args []js.Value) (string, error) {
|
||||||
js.Global().Get("console").Call("log", "💓 Health check called")
|
js.Global().Get("console").Call("log", "💓 Health check called")
|
||||||
return `{"status": "healthy", "message": "Go WASM module with Goja is running"}`, nil
|
return `{"status": "healthy", "message": "Go WASM module is running"}`, nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
js.Global().Get("console").Call("log", "✅ Functions exposed to JavaScript:")
|
js.Global().Get("console").Call("log", "✅ Functions exposed to JavaScript:")
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ name = "quickjs-transform"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[[bin]]
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
name = "quickjs_transform"
|
name = "quickjs_transform"
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rquickjs = { version = "0.6", default-features = false, features = ["bindgen"] }
|
rquickjs = { version = "0.6", default-features = false, features = ["bindgen"] }
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ const importObject = {
|
|||||||
const wasmModule = await WebAssembly.instantiate(wasmBytes, importObject);
|
const wasmModule = await WebAssembly.instantiate(wasmBytes, importObject);
|
||||||
const wasmInstance = wasmModule.instance;
|
const wasmInstance = wasmModule.instance;
|
||||||
|
|
||||||
// Initialize WASI - now that we have a _start function, we can call start
|
// Initialize WASI even though we don't have _start
|
||||||
wasi.start(wasmInstance);
|
// This is needed for memory operations to work properly
|
||||||
|
wasi.initialize(wasmInstance);
|
||||||
|
|
||||||
// Helper functions to work with C strings
|
// Helper functions to work with C strings
|
||||||
function allocateString(wasmInstance, str) {
|
function allocateString(wasmInstance, str) {
|
||||||
|
|||||||
96
implementations/quickjs/src/lib.rs
Normal file
96
implementations/quickjs/src/lib.rs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
use rquickjs::{Context, Runtime, Value};
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
fn execute_js_internal(js_code: &str, input_data: &str) -> Result<String, String> {
|
||||||
|
let rt = Runtime::new().map_err(|e| format!("Failed to create runtime: {}", e))?;
|
||||||
|
let ctx = Context::full(&rt).map_err(|e| format!("Failed to create context: {}", e))?;
|
||||||
|
|
||||||
|
ctx.with(|ctx| {
|
||||||
|
// Set up the input data as a global variable
|
||||||
|
let setup_code = format!("const inputData = `{}`;", input_data.replace('`', r#"\`"#));
|
||||||
|
if let Err(e) = ctx.eval::<(), _>(setup_code.as_bytes()) {
|
||||||
|
return Err(format!("Failed to set up input data: {:?}", e));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the user's JavaScript code
|
||||||
|
let result: Result<Value, _> = ctx.eval(js_code.as_bytes());
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(value) => {
|
||||||
|
if let Some(s) = value.as_string() {
|
||||||
|
Ok(s.to_string().unwrap_or_default())
|
||||||
|
} else if let Some(n) = value.as_number() {
|
||||||
|
Ok(n.to_string())
|
||||||
|
} else if let Some(b) = value.as_bool() {
|
||||||
|
Ok(b.to_string())
|
||||||
|
} else if value.is_null() {
|
||||||
|
Ok("null".to_string())
|
||||||
|
} else if value.is_undefined() {
|
||||||
|
Ok("undefined".to_string())
|
||||||
|
} else {
|
||||||
|
Ok(format!("{:?}", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Err(format!("JavaScript execution error: {:?}", e)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// transform_data that accepts a null-terminated C string.
|
||||||
|
/// Returns a newly allocated C string (must be freed with free_string).
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn transform_data(input_ptr: *const c_char) -> *mut c_char {
|
||||||
|
if input_ptr.is_null() {
|
||||||
|
return std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
// Safety: input_ptr must point to a valid C string.
|
||||||
|
let input = unsafe { CStr::from_ptr(input_ptr) }.to_string_lossy().into_owned();
|
||||||
|
// Default transform: JSON parse then stringify (no-op normalize)
|
||||||
|
let js_code = "JSON.stringify(JSON.parse(inputData))";
|
||||||
|
match execute_js_internal(js_code, &input) {
|
||||||
|
Ok(s) => CString::new(s).map(|c| c.into_raw()).unwrap_or(std::ptr::null_mut()),
|
||||||
|
Err(_) => std::ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// transform_data that accepts (ptr, len) input.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn transform_data_len(ptr: *const u8, len: usize) -> *mut c_char {
|
||||||
|
if ptr.is_null() {
|
||||||
|
return std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
// Safety: ptr must point to a valid buffer of length len.
|
||||||
|
let input = unsafe { std::slice::from_raw_parts(ptr, len) };
|
||||||
|
let input = String::from_utf8_lossy(input).into_owned();
|
||||||
|
let js_code = "JSON.stringify(JSON.parse(inputData))";
|
||||||
|
match execute_js_internal(js_code, &input) {
|
||||||
|
Ok(s) => CString::new(s).map(|c| c.into_raw()).unwrap_or(std::ptr::null_mut()),
|
||||||
|
Err(_) => std::ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute arbitrary JS with inputData available as a global.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn execute_js(js_ptr: *const c_char, input_ptr: *const c_char) -> *mut c_char {
|
||||||
|
if js_ptr.is_null() || input_ptr.is_null() {
|
||||||
|
return std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
let js = unsafe { CStr::from_ptr(js_ptr) }.to_string_lossy().into_owned();
|
||||||
|
let input = unsafe { CStr::from_ptr(input_ptr) }.to_string_lossy().into_owned();
|
||||||
|
match execute_js_internal(&js, &input) {
|
||||||
|
Ok(s) => CString::new(s).map(|c| c.into_raw()).unwrap_or(std::ptr::null_mut()),
|
||||||
|
Err(_) => std::ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Free a C string previously returned by this module.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn free_string(ptr: *mut c_char) {
|
||||||
|
if !ptr.is_null() {
|
||||||
|
// Safety: must only be called on pointers returned by our functions.
|
||||||
|
unsafe { let _ = CString::from_raw(ptr); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: malloc is already provided by the libc runtime, so we don't define our own
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
use rquickjs::{Runtime, Context, Value};
|
|
||||||
use std::env;
|
|
||||||
use std::io::{self, Read};
|
|
||||||
|
|
||||||
fn execute_js(js_code: &str, input_data: &str) -> Result<String, String> {
|
|
||||||
let rt = Runtime::new().map_err(|e| format!("Failed to create runtime: {}", e))?;
|
|
||||||
let ctx = Context::full(&rt).map_err(|e| format!("Failed to create context: {}", e))?;
|
|
||||||
|
|
||||||
ctx.with(|ctx| {
|
|
||||||
// Set up the input data as a global variable
|
|
||||||
let setup_code = format!("const inputData = `{}`;", input_data.replace('`', r#"\`"#));
|
|
||||||
if ctx.eval::<(), _>(setup_code.as_bytes()).is_err() {
|
|
||||||
return Err("Failed to set up input data".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the user's JavaScript code
|
|
||||||
let result: Result<Value, _> = ctx.eval(js_code.as_bytes());
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(value) => {
|
|
||||||
if let Some(s) = value.as_string() {
|
|
||||||
Ok(s.to_string().unwrap_or_default())
|
|
||||||
} else if let Some(n) = value.as_number() {
|
|
||||||
Ok(n.to_string())
|
|
||||||
} else if let Some(b) = value.as_bool() {
|
|
||||||
Ok(b.to_string())
|
|
||||||
} else if value.is_null() {
|
|
||||||
Ok("null".to_string())
|
|
||||||
} else if value.is_undefined() {
|
|
||||||
Ok("undefined".to_string())
|
|
||||||
} else {
|
|
||||||
Ok(format!("{:?}", value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => Err(format!("JavaScript execution error: {:?}", e))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args: Vec<String> = env::args().collect();
|
|
||||||
|
|
||||||
// Require JavaScript code as argument
|
|
||||||
if args.len() < 2 {
|
|
||||||
eprintln!("Usage: wasmer run quickjs.wasm 'JavaScript code' [input_data]");
|
|
||||||
eprintln!("Example: echo '{{\"test\": 123}}' | wasmer run quickjs.wasm 'JSON.stringify({{result: JSON.parse(inputData)}})'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let js_code = args[1].clone();
|
|
||||||
|
|
||||||
// Read input data from stdin
|
|
||||||
let mut input_data = String::new();
|
|
||||||
if let Err(e) = io::stdin().read_to_string(&mut input_data) {
|
|
||||||
eprintln!("Error reading input: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim whitespace from input
|
|
||||||
input_data = input_data.trim().to_string();
|
|
||||||
|
|
||||||
// Execute JavaScript with the input data
|
|
||||||
match execute_js(&js_code, &input_data) {
|
|
||||||
Ok(result) => println!("{}", result),
|
|
||||||
Err(e) => eprintln!("Error: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,6 +23,8 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^11.0.3",
|
"glob": "^11.0.3",
|
||||||
|
"javy": "^0.1.2",
|
||||||
|
"porffor": "^0.60.6",
|
||||||
"vitest": "^3.1.2"
|
"vitest": "^3.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -13,24 +13,25 @@ export function callGoFunction(fn: any, ...args: any[]): Promise<string> {
|
|||||||
// Check if current build is using Javy
|
// Check if current build is using Javy
|
||||||
async function isJavyBuild(): Promise<boolean> {
|
async function isJavyBuild(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
// Check if javy-adapter.js exists in the current implementation
|
// Check if the Javy dynamic WASM exists in the expected location
|
||||||
const javyAdapterPath = path.join(
|
// This is only present when Javy build was run
|
||||||
|
const javyDynamicPath = path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
"implementations/javy/javy-adapter.js"
|
"implementations/javy/transform_dynamic.wasm"
|
||||||
);
|
);
|
||||||
await access(javyAdapterPath);
|
await access(javyDynamicPath);
|
||||||
|
|
||||||
|
// Additionally check if the current lib.wasm.gz was built from Javy
|
||||||
|
// by checking if plugin.wasm exists (only created by Javy build)
|
||||||
|
const javyPluginPath = path.join(
|
||||||
|
__dirname,
|
||||||
|
"implementations/javy/plugin.wasm"
|
||||||
|
);
|
||||||
|
await access(javyPluginPath);
|
||||||
|
|
||||||
// Also check if the current symlink points to javy implementation
|
|
||||||
const mainGoPath = path.join(__dirname, "main.go");
|
|
||||||
try {
|
|
||||||
const mainGoContent = await readFile(mainGoPath, "utf8");
|
|
||||||
// This is a simple heuristic - in a real implementation you might check the symlink target
|
|
||||||
return false; // Go implementations will have main.go
|
|
||||||
} catch {
|
|
||||||
// If main.go doesn't exist, might be a Javy build
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
|
// If Javy artifacts don't exist, this is not a Javy build
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user