통합
WASM Integration Guide
This guide explains how to integrate the actual Vais compiler (vaisc) with the playground using WebAssembly.
Overview
The playground currently uses a mock compiler for demonstration. To enable real compilation:
- Compile
vaiscto WASM target - Create JavaScript bindings
- Load and initialize the WASM module
- Update the compiler interface
Step 1: Compile vaisc to WASM
Prerequisites
# Install wasm32 target
rustup target add wasm32-unknown-unknown
# Install wasm-bindgen CLI
cargo install wasm-bindgen-cli
Build Configuration
Create a new crate for WASM bindings:
cd ../crates
cargo new vais-wasm --lib
Edit crates/vais-wasm/Cargo.toml:
[package]
name = "vais-wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
vais-lexer = { path = "../vais-lexer" }
vais-parser = { path = "../vais-parser" }
vais-types = { path = "../vais-types" }
vais-codegen = { path = "../vais-codegen" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.6"
WASM Bindings
Create crates/vais-wasm/src/lib.rs:
use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct CompileResult {
success: bool,
ir: Option<String>,
errors: Vec<CompileError>,
warnings: Vec<CompileWarning>,
}
#[derive(Serialize, Deserialize)]
pub struct CompileError {
line: usize,
column: usize,
message: String,
}
#[derive(Serialize, Deserialize)]
pub struct CompileWarning {
line: usize,
column: usize,
message: String,
}
#[wasm_bindgen]
pub fn compile_vais(source: &str) -> JsValue {
let result = compile_vais_internal(source);
serde_wasm_bindgen::to_value(&result).unwrap()
}
fn compile_vais_internal(source: &str) -> CompileResult {
// Tokenize
let tokens = match vais_lexer::tokenize(source) {
Ok(t) => t,
Err(e) => {
return CompileResult {
success: false,
ir: None,
errors: vec![CompileError {
line: 0,
column: 0,
message: format!("Lexer error: {}", e),
}],
warnings: vec![],
};
}
};
// Parse
let ast = match vais_parser::parse(source) {
Ok(a) => a,
Err(e) => {
return CompileResult {
success: false,
ir: None,
errors: vec![CompileError {
line: 0,
column: 0,
message: format!("Parse error: {}", e),
}],
warnings: vec![],
};
}
};
// Type check
let mut checker = vais_types::TypeChecker::new();
if let Err(e) = checker.check_module(&ast) {
return CompileResult {
success: false,
ir: None,
errors: vec![CompileError {
line: 0,
column: 0,
message: format!("Type error: {}", e),
}],
warnings: vec![],
};
}
// Generate IR
let mut codegen = vais_codegen::CodeGenerator::new("playground");
let ir = match codegen.generate_module(&ast) {
Ok(ir) => ir,
Err(e) => {
return CompileResult {
success: false,
ir: None,
errors: vec![CompileError {
line: 0,
column: 0,
message: format!("Codegen error: {}", e),
}],
warnings: vec![],
};
}
};
CompileResult {
success: true,
ir: Some(ir),
errors: vec![],
warnings: vec![],
}
}
#[wasm_bindgen(start)]
pub fn init() {
// Initialize panic hook for better error messages
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
Build WASM Module
cd crates/vais-wasm
wasm-pack build --target web --out-dir ../../playground/public/wasm
Step 2: Update JavaScript Compiler Interface
Modify src/compiler.js:
import init, { compile_vais } from '../public/wasm/vais_wasm.js';
export class VaisCompiler {
constructor() {
this.isReady = false;
this.wasmModule = null;
}
async initialize() {
try {
// Initialize WASM module
this.wasmModule = await init();
this.isReady = true;
return true;
} catch (error) {
console.error('Failed to initialize WASM:', error);
throw new Error('WASM initialization failed: ' + error.message);
}
}
async compile(sourceCode) {
if (!this.isReady) {
await this.initialize();
}
try {
// Call WASM compile function
const result = compile_vais(sourceCode);
return result;
} catch (error) {
return {
success: false,
errors: [{
line: 0,
column: 0,
message: `Compilation error: ${error.message}`
}],
warnings: [],
ir: null
};
}
}
// ... rest of the methods
}
Step 3: Execution Engine
For executing compiled code, you have two options:
Option A: Compile to WASM and Execute
- Modify the compiler to generate WASM directly (instead of LLVM IR)
- Load the generated WASM module
- Execute the exported functions
Option B: LLVM IR Interpreter
- Use an LLVM IR interpreter written in JavaScript/WASM
- Feed the generated IR to the interpreter
- Capture stdout/stderr
Option C: Server-Side Execution
- Send the code to a backend server
- Compile and execute on the server
- Return the output to the frontend
Example server setup:
// In compiler.js
async executeOnServer(sourceCode) {
try {
const response = await fetch('/api/compile', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ code: sourceCode })
});
const result = await response.json();
return result;
} catch (error) {
return {
success: false,
output: null,
error: error.message
};
}
}
Step 4: Testing
# Test WASM build
cd crates/vais-wasm
wasm-pack test --node
# Test in browser
cd ../../playground
npm run dev
Performance Considerations
- Lazy Loading: Load WASM module only when needed
- Worker Threads: Run compilation in a Web Worker
- Caching: Cache compiled modules
- Streaming: Use streaming compilation for large files
Worker Implementation
Create src/compiler.worker.js:
import init, { compile_vais } from '../public/wasm/vais_wasm.js';
let initialized = false;
self.onmessage = async function(e) {
const { id, type, data } = e.data;
if (type === 'init') {
try {
await init();
initialized = true;
self.postMessage({ id, type: 'init', success: true });
} catch (error) {
self.postMessage({ id, type: 'init', success: false, error: error.message });
}
}
if (type === 'compile') {
if (!initialized) {
self.postMessage({ id, type: 'compile', success: false, error: 'Not initialized' });
return;
}
try {
const result = compile_vais(data.source);
self.postMessage({ id, type: 'compile', result });
} catch (error) {
self.postMessage({ id, type: 'compile', success: false, error: error.message });
}
}
};
Update src/compiler.js:
export class VaisCompiler {
constructor() {
this.worker = new Worker(new URL('./compiler.worker.js', import.meta.url), {
type: 'module'
});
this.requestId = 0;
this.pending = new Map();
this.worker.onmessage = (e) => {
const { id, type, result, error } = e.data;
const resolve = this.pending.get(id);
if (resolve) {
if (error) {
resolve({ success: false, error });
} else {
resolve(result);
}
this.pending.delete(id);
}
};
}
async initialize() {
return new Promise((resolve) => {
const id = this.requestId++;
this.pending.set(id, resolve);
this.worker.postMessage({ id, type: 'init' });
});
}
async compile(sourceCode) {
return new Promise((resolve) => {
const id = this.requestId++;
this.pending.set(id, resolve);
this.worker.postMessage({
id,
type: 'compile',
data: { source: sourceCode }
});
});
}
}
Debugging
Enable Debug Output
// In lib.rs
#[wasm_bindgen]
pub fn set_debug_mode(enabled: bool) {
// Enable verbose logging
}
Console Logging
use web_sys::console;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
// Usage
log(&format!("Compiling: {} lines", source.lines().count()));
Deployment
Build for Production
# Build WASM with optimizations
cd crates/vais-wasm
wasm-pack build --target web --release --out-dir ../../playground/public/wasm
# Optimize WASM binary
wasm-opt -Oz -o vais_wasm_bg.wasm.opt vais_wasm_bg.wasm
# Build playground
cd ../../playground
npm run build
Size Optimization
Add to Cargo.toml:
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = true
CDN Hosting
Upload WASM files to a CDN for faster loading:
// In compiler.js
const WASM_URL = 'https://cdn.example.com/vais_wasm_bg.wasm';
async initialize() {
const response = await fetch(WASM_URL);
const bytes = await response.arrayBuffer();
this.wasmModule = await init(bytes);
}
Troubleshooting
Common Issues
- WASM not loading: Check MIME type is
application/wasm - Import errors: Ensure wasm-bindgen versions match
- Memory issues: Increase WASM memory limit
- CORS errors: Configure server headers correctly
Browser Console
// Check WASM support
console.log('WASM support:', typeof WebAssembly !== 'undefined');
// Log module size
fetch('/wasm/vais_wasm_bg.wasm')
.then(r => r.arrayBuffer())
.then(b => console.log('WASM size:', (b.byteLength / 1024).toFixed(2), 'KB'));