provisioning-outreach/presentations/rust-laspalmas-250926/auroraframe/framework/optimizers/optimize-wasm.nu

404 lines
No EOL
14 KiB
Text

#!/usr/bin/env nu
# WebAssembly optimization and compilation module
# Supports Rust, C/C++, AssemblyScript, and Go compilation to WASM
# Main WASM compilation function
export def compile_wasm [config: record, dev: bool, verbose: bool] {
if not ("wasm" in ($config | columns)) or not $config.wasm.enabled {
if $verbose { print "📦 WASM not enabled, skipping..." }
return { size: 0, modules: [] }
}
if $verbose { print "🦀 Compiling WebAssembly modules..." }
let wasm_config = $config.wasm
mut compiled_modules = []
mut total_size = 0
# Validate WASM build environment
let toolchain_check = (validate_wasm_toolchain $wasm_config $verbose)
if not $toolchain_check.valid {
print $"❌ WASM toolchain validation failed: ($toolchain_check.error)"
return { size: 0, modules: [] }
}
# Create WASM output directory
mkdir $wasm_config.output_dir
# Compile each WASM module
for module in $wasm_config.modules {
let module_result = (compile_wasm_module $module $wasm_config $dev $verbose)
if $module_result.success {
$compiled_modules = ($compiled_modules | append $module_result)
$total_size = ($total_size + $module_result.size)
} else {
print $"❌ Failed to compile WASM module: ($module.name)"
}
}
# Generate JavaScript loader if modules were compiled
if ($compiled_modules | length) > 0 {
let loader_result = (generate_wasm_loader $compiled_modules $wasm_config $verbose)
if $loader_result.success {
$total_size = ($total_size + $loader_result.size)
}
}
if $verbose {
print $"✅ Compiled ($compiled_modules | length) WASM modules, total size: ($total_size) bytes"
}
{
size: $total_size,
modules: $compiled_modules
}
}
# Compile individual WASM module
def compile_wasm_module [module: record, wasm_config: record, dev: bool, verbose: bool] {
let source_path = ($wasm_config.source_dir | path join $module.source)
if not ($source_path | path exists) {
return {
name: $module.name,
success: false,
error: $"Source file not found: ($source_path)"
}
}
let output_path = ($wasm_config.output_dir | path join $"($module.name).wasm")
match $module.lang {
"rust" => { compile_rust_wasm $module $source_path $output_path $wasm_config $dev $verbose }
"c" | "cpp" | "c++" => { compile_c_wasm $module $source_path $output_path $wasm_config $dev $verbose }
"assemblyscript" | "as" => { compile_assemblyscript_wasm $module $source_path $output_path $wasm_config $dev $verbose }
"go" => { compile_go_wasm $module $source_path $output_path $wasm_config $dev $verbose }
_ => {
return {
name: $module.name,
success: false,
error: $"Unsupported language: ($module.lang)"
}
}
}
}
# Compile Rust to WASM
def compile_rust_wasm [module: record, source_path: string, output_path: string, wasm_config: record, dev: bool, verbose: bool] {
let rust_config = if "rust" in ($wasm_config | columns) { $wasm_config.rust } else { {} }
let toolchain = if "toolchain" in ($rust_config | columns) { $rust_config.toolchain } else { "wasm-pack" }
let target = if "target" in ($rust_config | columns) { $rust_config.target } else { "web" }
if $verbose { print $" 🦀 Compiling Rust module: ($module.name)" }
match $toolchain {
"wasm-pack" => {
let build_args = if $dev { ["--dev"] } else { ["--release"] }
let scope = if "scope" in ($rust_config | columns) { ["--scope", $rust_config.scope] } else { [] }
try {
let args = ["build", "--target", $target, "--out-dir", ($wasm_config.output_dir | path join $module.name)] ++ $build_args ++ $scope ++ [($source_path | path dirname)]
run-external "wasm-pack" ...$args
# Copy main WASM file to expected location
let pkg_wasm = ($wasm_config.output_dir | path join $module.name | path join $"($module.name)_bg.wasm")
if ($pkg_wasm | path exists) {
cp $pkg_wasm $output_path
}
let wasm_size = if ($output_path | path exists) {
(ls $output_path | get size | first | into int)
} else { 0 }
{
name: $module.name,
success: true,
path: $output_path,
size: $wasm_size,
lang: "rust",
loader_path: ($wasm_config.output_dir | path join $module.name | path join $"($module.name).js")
}
} catch { |e|
{
name: $module.name,
success: false,
error: $"wasm-pack build failed: ($e.msg)"
}
}
}
"cargo" => {
try {
# Use cargo for basic WASM compilation
let release_args = if $dev { [] } else { ["--release"] }
let args = ["build", "--target", "wasm32-unknown-unknown"] ++ $release_args
run-external "cargo" ...$args
# Find compiled WASM file
let target_dir = if $dev { "debug" } else { "release" }
let cargo_wasm = ($source_path | path dirname | path join "target" "wasm32-unknown-unknown" $target_dir $"($module.name).wasm")
if ($cargo_wasm | path exists) {
cp $cargo_wasm $output_path
let wasm_size = (ls $output_path | get size | first | into int)
{
name: $module.name,
success: true,
path: $output_path,
size: $wasm_size,
lang: "rust"
}
} else {
{
name: $module.name,
success: false,
error: "Compiled WASM file not found"
}
}
} catch { |e|
{
name: $module.name,
success: false,
error: $"cargo build failed: ($e.msg)"
}
}
}
_ => {
{
name: $module.name,
success: false,
error: $"Unsupported Rust toolchain: ($toolchain)"
}
}
}
}
# Compile C/C++ to WASM using Emscripten
def compile_c_wasm [module: record, source_path: string, output_path: string, wasm_config: record, dev: bool, verbose: bool] {
if $verbose { print $" 🔧 Compiling C/C++ module: ($module.name)" }
let c_config = if "c" in ($wasm_config | columns) { $wasm_config.c } else { {} }
let optimization = if $dev { "-O0" } else { "-O3" }
let exports = if "exports" in ($c_config | columns) { $c_config.exports } else { ["main"] }
let export_args = ($exports | each { |fn| ["-s", $"EXPORTED_FUNCTIONS=['_($fn)']"] } | flatten)
try {
let args = [$source_path, "-o", $output_path, $optimization, "-s", "WASM=1", "-s", "MODULARIZE=1", "-s", "EXPORT_NAME=initWasm"] ++ $export_args
run-external "emcc" ...$args
let wasm_size = if ($output_path | path exists) {
(ls $output_path | get size | first | into int)
} else { 0 }
{
name: $module.name,
success: true,
path: $output_path,
size: $wasm_size,
lang: "c"
}
} catch { |e|
{
name: $module.name,
success: false,
error: $"emcc compilation failed: ($e.msg)"
}
}
}
# Compile AssemblyScript to WASM
def compile_assemblyscript_wasm [module: record, source_path: string, output_path: string, wasm_config: record, dev: bool, verbose: bool] {
if $verbose { print $" 📝 Compiling AssemblyScript module: ($module.name)" }
let optimization = if $dev { "" } else { "--optimize" }
let args = if $dev { [] } else { ["--optimize"] }
try {
let asc_args = [$source_path, "--outFile", $output_path, "--bindings", "esm"] ++ $args
run-external "asc" ...$asc_args
let wasm_size = if ($output_path | path exists) {
(ls $output_path | get size | first | into int)
} else { 0 }
{
name: $module.name,
success: true,
path: $output_path,
size: $wasm_size,
lang: "assemblyscript"
}
} catch { |e|
{
name: $module.name,
success: false,
error: $"AssemblyScript compilation failed: ($e.msg)"
}
}
}
# Compile Go to WASM
def compile_go_wasm [module: record, source_path: string, output_path: string, wasm_config: record, dev: bool, verbose: bool] {
if $verbose { print $" 🐹 Compiling Go module: ($module.name)" }
try {
# Set environment variables for Go WASM compilation
with-env {
GOOS: "js",
GOARCH: "wasm"
} {
run-external "go" "build" "-o" $output_path $source_path
}
let wasm_size = if ($output_path | path exists) {
(ls $output_path | get size | first | into int)
} else { 0 }
{
name: $module.name,
success: true,
path: $output_path,
size: $wasm_size,
lang: "go"
}
} catch { |e|
{
name: $module.name,
success: false,
error: $"Go compilation failed: ($e.msg)"
}
}
}
# Validate WASM toolchain
def validate_wasm_toolchain [wasm_config: record, verbose: bool] {
mut missing_tools = []
# Check for common WASM tools
let tools_to_check = [
{ name: "wasm-pack", command: "wasm-pack", required_for: "Rust" },
{ name: "cargo", command: "cargo", required_for: "Rust" },
{ name: "emcc", command: "emcc", required_for: "C/C++" },
{ name: "asc", command: "asc", required_for: "AssemblyScript" },
{ name: "go", command: "go", required_for: "Go" },
{ name: "wasm-opt", command: "wasm-opt", required_for: "Optimization" }
]
for tool in $tools_to_check {
let available = (which $tool.command | length) > 0
if not $available {
$missing_tools = ($missing_tools | append $tool)
} else if $verbose {
print $" ✅ ($tool.name) available"
}
}
# Check which languages are actually needed
let needed_languages = if "modules" in ($wasm_config | columns) {
($wasm_config.modules | get lang | uniq)
} else { [] }
let critical_missing = ($missing_tools | where { |tool|
$needed_languages | any { |lang| $lang in $tool.required_for }
})
if ($critical_missing | length) > 0 {
{
valid: false,
error: $"Missing required tools: ($critical_missing | get name | str join ', ')"
}
} else {
{ valid: true }
}
}
# Generate JavaScript loader for WASM modules
def generate_wasm_loader [modules: list, wasm_config: record, verbose: bool] {
if $verbose { print " 📜 Generating WASM loader..." }
# Find framework root by looking for framework directory
let current_dir = $env.PWD
let framework_templates = ($current_dir | path join "framework" "templates")
let template_path = ($framework_templates | path join "wasm-loader.js.template")
if not ($template_path | path exists) {
return {
success: false,
error: $"WASM loader template not found: ($template_path)"
}
}
# Create module loading calls
let module_calls = ($modules | each { |mod|
let module_name = $mod.name
$" this.loadModule\('($module_name)', './($module_name).wasm'\),"
} | str join "\n")
# Load template and replace placeholder
let template_content = (open $template_path)
let loader_content = ($template_content | str replace "{{MODULE_PROMISES}}" $module_calls)
let loader_path = ($wasm_config.output_dir | path join "wasm-loader.js")
$loader_content | save --force $loader_path
if $verbose { print $" ✅ Generated WASM loader: ($loader_path)" }
{
success: true,
path: $loader_path,
size: ($loader_content | str length)
}
}
# Optimize WASM with wasm-opt (if available)
export def optimize_wasm_binary [wasm_path: string, config: record] {
let wasm_opt_available = (which wasm-opt | length) > 0
if not $wasm_opt_available {
return { optimized: false, reason: "wasm-opt not available" }
}
let wasm_config = if "wasm" in ($config | columns) { $config.wasm } else { {} }
let optimization = if "optimization" in ($wasm_config | columns) { $wasm_config.optimization } else { {} }
let opt_level = if "opt_level" in ($optimization | columns) { $optimization.opt_level } else { 3 }
let shrink_level = if "shrink_level" in ($optimization | columns) { $optimization.shrink_level } else { 2 }
let temp_path = $"($wasm_path).opt"
try {
let wasm_opt_args = [$wasm_path, "-o", $temp_path, $"-O($opt_level)", $"-s($shrink_level)", "--enable-simd", "--enable-bulk-memory"]
run-external "wasm-opt" ...$wasm_opt_args
# Replace original with optimized version
mv $temp_path $wasm_path
{ optimized: true }
} catch { |e|
# Clean up temp file if it exists
if ($temp_path | path exists) {
rm $temp_path
}
{ optimized: false, reason: $"wasm-opt failed: ($e.msg)" }
}
}
# Get WASM module information
export def analyze_wasm [wasm_path: string] {
if not ($wasm_path | path exists) {
return null
}
let size = (ls $wasm_path | get size | first | into int)
let size_kb = ($size / 1024 | math round -p 1)
{
path: $wasm_path,
size: $size,
size_kb: $size_kb,
exists: true
}
}