Skip to content

Commit f2b49e4

Browse files
committed
fix(runtime): remove WASIP2 interception and fix cross-instance CallIndirect
Major changes per CLAUDE.md compliance: - Remove ~383 lines of WASIP2-CANONICAL interception block that was bypassing component adapter modules for P2→P1 translation - Add cross-instance import checking in CallIndirect handler so indirect calls to imported functions redirect to linked instances - Remove hardcoded fallback mappings that violated NO FALLBACK rule - Convert all debug println! to proper tracing framework calls - Fix 18 syntax errors in platform_stubs.rs (missing parentheses) - Fix .unwrap_or fallback patterns to return proper errors instead of silently substituting defaults (FAIL LOUD AND EARLY) Also includes component model improvements: - Enhanced canonical ABI implementation - Improved component instantiation with proper export mapping - Resource management enhancements - WAST validator improvements
1 parent d3e5674 commit f2b49e4

File tree

79 files changed

+12210
-2374
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+12210
-2374
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,18 @@ jobs:
102102
run: cargo fmt --all -- --check
103103
- name: Run clippy checks
104104
run: cargo clippy --workspace --all-targets -- -D warnings
105+
- name: Check for WASI stub implementations in engine (architectural invariant)
106+
run: |
107+
# Engine must NOT contain hardcoded WASI function implementations
108+
# Per CLAUDE.md: Engine has ZERO WASI-specific stub code - all dispatch goes through wrt-wasi
109+
# Pattern matches: match arms like ("wasi:cli/stdout", "get-stdout") => { ... }
110+
if grep -nE '^\s*\("wasi:' wrt-runtime/src/stackless/engine.rs; then
111+
echo "ERROR: Hardcoded WASI function implementations found in engine.rs"
112+
echo "All WASI dispatch must go through wrt-wasi/src/dispatcher.rs"
113+
echo "Engine should only use WasiDispatcher, not implement WASI functions directly"
114+
exit 1
115+
fi
116+
echo "Engine architecture check passed - no hardcoded WASI stubs in engine"
105117
106118
core_tests_and_analysis:
107119
name: Core Tests, Analysis & Coverage

cargo-wrt/src/main.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3058,7 +3058,6 @@ async fn cmd_testsuite(
30583058
test_timeout_ms: u64,
30593059
global: &mut GlobalArgs,
30603060
) -> Result<()> {
3061-
eprintln!("DEBUG: cmd_testsuite called with run_wast={}", run_wast);
30623061
if clean {
30633062
println!("{} Cleaning extracted test files...", "🧹".bright_blue());
30643063
// TODO: Implement cleaning through wrt-build-core
@@ -3088,19 +3087,9 @@ async fn cmd_testsuite(
30883087
WastTestRunner,
30893088
};
30903089

3091-
eprintln!("DEBUG: run_wast is true, starting WAST test suite...");
3092-
3093-
// Initialize the memory system before running WAST tests
3094-
eprintln!("DEBUG: Initializing memory system...");
3095-
// The memory system initialization is handled by wrt-build-core internally
3096-
// No need to initialize it here as the WastEngine will do it when needed
3097-
eprintln!("DEBUG: Memory system initialization delegated to WastEngine");
3098-
30993090
println!("{} Running WAST test suite...", "🧪".bright_blue());
31003091

31013092
let workspace_root = build_system.workspace_root();
3102-
eprintln!("DEBUG: workspace_root = {:?}", workspace_root);
3103-
eprintln!("DEBUG: wast_dir = {:?}", wast_dir);
31043093
let test_directory = workspace_root.join(&wast_dir);
31053094

31063095
if !test_directory.exists() {
@@ -3126,10 +3115,8 @@ async fn cmd_testsuite(
31263115
};
31273116

31283117
// Create and run WAST test runner
3129-
eprintln!("DEBUG: About to create WastTestRunner with config...");
31303118
let mut runner =
31313119
WastTestRunner::new(config).context("Failed to create WAST test runner")?;
3132-
eprintln!("DEBUG: WastTestRunner created successfully");
31333120
let start_time = std::time::Instant::now();
31343121

31353122
match runner.run_all_tests() {

wrt-build-core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ version.workspace = true
88

99
[features]
1010
default = ["std"]
11-
std = ["anyhow/std", "clap/std", "wrt-runtime/std"]
11+
std = ["anyhow/std", "clap/std", "wrt-runtime/std", "wrt-decoder/std"]
1212
no_std = []
1313

1414
[dependencies]

wrt-build-core/src/wast.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -372,10 +372,6 @@ impl WastTestRunner {
372372
Ok(directive_info) => {
373373
if directive_info.result == TestResult::Failed {
374374
file_status = TestResult::Failed;
375-
// Print error details for debugging
376-
if let Some(ref err_msg) = directive_info.error_message {
377-
eprintln!("[FAIL] {}: {}", directive_info.directive_name, err_msg);
378-
}
379375
}
380376
directive_results.push(directive_info);
381377
},
@@ -687,13 +683,22 @@ impl WastTestRunner {
687683
},
688684
Err(e) => {
689685
self.stats.failed += 1;
686+
// Extract function name for better error context
687+
let func_name = match exec {
688+
WastExecute::Invoke(invoke) => invoke.name.to_string(),
689+
WastExecute::Get { global, .. } => format!("get {}", global),
690+
_ => "unknown".to_string(),
691+
};
690692
Ok(WastDirectiveInfo {
691693
test_type: WastTestType::Correctness,
692694
directive_name: "assert_return".to_string(),
693695
requires_module_state: true,
694696
modifies_engine_state: false,
695697
result: TestResult::Failed,
696-
error_message: Some(format!("Function execution failed: {}", e)),
698+
error_message: Some(format!(
699+
"Function '{}' failed: {}",
700+
func_name, e
701+
)),
697702
})
698703
},
699704
}
@@ -818,7 +823,8 @@ impl WastTestRunner {
818823
},
819824
Err(validation_error) => {
820825
// Module validation failed as expected
821-
let error_msg = validation_error.to_string().to_lowercase();
826+
// Use debug format to get full error chain, not just top-level message
827+
let error_msg = format!("{:?}", validation_error).to_lowercase();
822828
let expected_msg = expected_message.to_lowercase();
823829

824830
if error_msg.contains(&expected_msg)
@@ -1645,6 +1651,8 @@ fn contains_validation_keyword(error_msg: &str, expected_msg: &str) -> bool {
16451651
"duplicate",
16461652
"import",
16471653
"export",
1654+
"memory size",
1655+
"must be at most",
16481656
];
16491657

16501658
validation_keywords

wrt-build-core/src/wast_validator.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,21 @@ enum FrameType {
7171
/// Validator for WebAssembly modules
7272
pub struct WastModuleValidator;
7373

74+
/// WebAssembly memory limits
75+
/// Max memory pages is 65536 (4GB with 64KB pages)
76+
const WASM_MAX_MEMORY_PAGES: u32 = 65536;
77+
7478
impl WastModuleValidator {
7579
/// Validate a module
7680
pub fn validate(module: &Module) -> Result<()> {
81+
// Validate memory limits
82+
Self::validate_memory_limits(module)?;
83+
84+
// Validate data segments - they require at least one memory to be defined
85+
if !module.data.is_empty() && !Self::has_memory(module) {
86+
return Err(anyhow!("unknown memory"));
87+
}
88+
7789
// Validate functions
7890
for (func_idx, func) in module.functions.iter().enumerate() {
7991
if let Err(e) = Self::validate_function(func_idx, func, module) {
@@ -90,6 +102,51 @@ impl WastModuleValidator {
90102
Ok(())
91103
}
92104

105+
/// Validate memory section limits
106+
fn validate_memory_limits(module: &Module) -> Result<()> {
107+
// Check imported memories
108+
for import in &module.imports {
109+
if let ImportDesc::Memory(memory) = &import.desc {
110+
// Check that min <= max if max is specified
111+
if let Some(max) = memory.limits.max {
112+
if memory.limits.min > max {
113+
return Err(anyhow!("size minimum must not be greater than maximum"));
114+
}
115+
}
116+
// Check memory size bounds (65536 pages max)
117+
if memory.limits.min > WASM_MAX_MEMORY_PAGES {
118+
return Err(anyhow!("memory size"));
119+
}
120+
if let Some(max) = memory.limits.max {
121+
if max > WASM_MAX_MEMORY_PAGES {
122+
return Err(anyhow!("memory size"));
123+
}
124+
}
125+
}
126+
}
127+
128+
// Check defined memories
129+
for memory in &module.memories {
130+
// Check that min <= max if max is specified
131+
if let Some(max) = memory.limits.max {
132+
if memory.limits.min > max {
133+
return Err(anyhow!("size minimum must not be greater than maximum"));
134+
}
135+
}
136+
// Check memory size bounds (65536 pages max)
137+
if memory.limits.min > WASM_MAX_MEMORY_PAGES {
138+
return Err(anyhow!("memory size"));
139+
}
140+
if let Some(max) = memory.limits.max {
141+
if max > WASM_MAX_MEMORY_PAGES {
142+
return Err(anyhow!("memory size"));
143+
}
144+
}
145+
}
146+
147+
Ok(())
148+
}
149+
93150
/// Validate a single function
94151
fn validate_function(func_idx: usize, func: &Function, module: &Module) -> Result<()> {
95152
// Get the function's type signature
@@ -523,8 +580,12 @@ impl WastModuleValidator {
523580
}
524581

525582
// Memory operations - Load instructions
583+
// All memory operations require at least one memory to be defined
526584
0x28 => {
527585
// i32.load - pop i32 address, push i32 value
586+
if !Self::has_memory(module) {
587+
return Err(anyhow!("unknown memory"));
588+
}
528589
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
529590
offset = new_offset;
530591
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
@@ -537,6 +598,9 @@ impl WastModuleValidator {
537598
}
538599
0x29 => {
539600
// i64.load - pop i32 address, push i64 value
601+
if !Self::has_memory(module) {
602+
return Err(anyhow!("unknown memory"));
603+
}
540604
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
541605
offset = new_offset;
542606
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
@@ -549,6 +613,9 @@ impl WastModuleValidator {
549613
}
550614
0x2A => {
551615
// f32.load - pop i32 address, push f32 value
616+
if !Self::has_memory(module) {
617+
return Err(anyhow!("unknown memory"));
618+
}
552619
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
553620
offset = new_offset;
554621
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
@@ -561,6 +628,9 @@ impl WastModuleValidator {
561628
}
562629
0x2B => {
563630
// f64.load - pop i32 address, push f64 value
631+
if !Self::has_memory(module) {
632+
return Err(anyhow!("unknown memory"));
633+
}
564634
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
565635
offset = new_offset;
566636
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
@@ -574,6 +644,9 @@ impl WastModuleValidator {
574644
0x2C..=0x35 => {
575645
// Extended load operations (load8, load16, load32, etc.)
576646
// All take i32 address and return the loaded value type
647+
if !Self::has_memory(module) {
648+
return Err(anyhow!("unknown memory"));
649+
}
577650
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
578651
offset = new_offset;
579652
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
@@ -592,6 +665,9 @@ impl WastModuleValidator {
592665
}
593666
0x36 => {
594667
// i32.store - pop i32 value and i32 address
668+
if !Self::has_memory(module) {
669+
return Err(anyhow!("unknown memory"));
670+
}
595671
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
596672
offset = new_offset;
597673
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
@@ -606,6 +682,9 @@ impl WastModuleValidator {
606682
}
607683
0x37 => {
608684
// i64.store - pop i64 value and i32 address
685+
if !Self::has_memory(module) {
686+
return Err(anyhow!("unknown memory"));
687+
}
609688
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
610689
offset = new_offset;
611690
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
@@ -620,6 +699,9 @@ impl WastModuleValidator {
620699
}
621700
0x38 => {
622701
// f32.store - pop f32 value and i32 address
702+
if !Self::has_memory(module) {
703+
return Err(anyhow!("unknown memory"));
704+
}
623705
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
624706
offset = new_offset;
625707
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
@@ -634,6 +716,9 @@ impl WastModuleValidator {
634716
}
635717
0x39 => {
636718
// f64.store - pop f64 value and i32 address
719+
if !Self::has_memory(module) {
720+
return Err(anyhow!("unknown memory"));
721+
}
637722
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
638723
offset = new_offset;
639724
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
@@ -648,6 +733,9 @@ impl WastModuleValidator {
648733
}
649734
0x3A..=0x3E => {
650735
// Extended store operations (store8, store16, store32)
736+
if !Self::has_memory(module) {
737+
return Err(anyhow!("unknown memory"));
738+
}
651739
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
652740
offset = new_offset;
653741
let (_, new_offset) = Self::parse_varuint32(code, offset)?;
@@ -666,6 +754,32 @@ impl WastModuleValidator {
666754
return Err(anyhow!("type mismatch"));
667755
}
668756
}
757+
0x3F => {
758+
// memory.size - push i32 (current memory size in pages)
759+
if !Self::has_memory(module) {
760+
return Err(anyhow!("unknown memory"));
761+
}
762+
// Skip reserved byte (memory index, always 0x00 in MVP)
763+
if offset < code.len() {
764+
offset += 1;
765+
}
766+
stack.push(StackType::I32);
767+
}
768+
0x40 => {
769+
// memory.grow - pop i32 (delta pages), push i32 (previous size or -1)
770+
if !Self::has_memory(module) {
771+
return Err(anyhow!("unknown memory"));
772+
}
773+
// Skip reserved byte (memory index, always 0x00 in MVP)
774+
if offset < code.len() {
775+
offset += 1;
776+
}
777+
let frame_height = Self::current_frame_height(&frames);
778+
if !Self::pop_type(&mut stack, StackType::I32, frame_height, Self::is_unreachable(&frames)) {
779+
return Err(anyhow!("type mismatch"));
780+
}
781+
stack.push(StackType::I32);
782+
}
669783

670784
// Variable operations
671785
0x20 => {
@@ -1503,6 +1617,21 @@ impl WastModuleValidator {
15031617
module.imports.iter().filter(|i| matches!(&i.desc, ImportDesc::Global(_))).count()
15041618
}
15051619

1620+
/// Count the number of memory imports in a module
1621+
fn count_memory_imports(module: &Module) -> usize {
1622+
module.imports.iter().filter(|i| matches!(&i.desc, ImportDesc::Memory(_))).count()
1623+
}
1624+
1625+
/// Get the total number of memories (imports + defined)
1626+
fn total_memories(module: &Module) -> usize {
1627+
Self::count_memory_imports(module) + module.memories.len()
1628+
}
1629+
1630+
/// Check if module has any memory defined (imported or local)
1631+
fn has_memory(module: &Module) -> bool {
1632+
Self::total_memories(module) > 0
1633+
}
1634+
15061635
/// Get the global type for a global index (accounting for imports)
15071636
/// Global indices include both imported and defined globals:
15081637
/// - Indices 0..N-1 are imported globals

wrt-component/src/builtins/async_ops.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,5 +418,3 @@ pub fn create_async_handlers(
418418

419419
handlers
420420
}
421-
422-
#[cfg(feature = "component-model-async")]

0 commit comments

Comments
 (0)