Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions DEBUGGING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Debugging Guide

This document outlines the workflow and tools for debugging the Webschembly compiler and JIT.

## Running Tests with Logs

To debug specific Scheme files or tests, use `just run` within the `webschembly-js` directory.

### Environment Variables

- `LOG_STDOUT=1`: Enables `log::debug!` output from the runtime and compiler (if configured). Use this to see runtime execution trace, JIT instantiation events, and error messages.
- `LOG=1`: Dumps the generated Intermediate Representation (IR) and Wasm binaries to the `webschembly-js/log/` directory.

### Command Examples

```bash
# Run a specific fixture with runtime logs
cd webschembly-js
just LOG_STDOUT=1 run ./fixtures/rec.scm

# Run with IR dumping
just LOG=1 run ./fixtures/rec.scm
```

## Analyzing Generated IR

When `LOG=1` is used, the `webschembly-js/log/` directory will contain files named with the timestamp and content description.

- `checks-TIMESTAMP-filename-0.ir`: Typically the main module IR (initial compilation).
- `checks-TIMESTAMP-filename-N.ir`: IR for JIT-compiled functions or stubs. `instantiate_func` or `instantiate_bb` calls in the runtime logs (seen with `LOG_STDOUT=1`) will reference `module_id` and `func_id`, which correlate to these files (though the mapping requires checking the `instantiate` log id vs file index).

**Tip**: Look for "instantiate: id:X" in the runtime logs. The corresponding IR file is often suffix `-X.ir`.

## Common Issues & Fixes

### "call target is not a closure"

This error occurs when the compiled code attempts to invoke a value as a closure, but the compile-time or run-time check fails.

- **Runtime**: The value on the stack is not a closure object (e.g., encoded as `val_type` mismatch).
- **Compile-time (Optimization)**: If the error appears unconditionally in the IR (e.g., `error "call target is not a closure"`), it means `constant_folding` or another pass determined the check `Is(Closure(None), target)` is false.
- _Watch out for_: Type mismatches in `InstrKind::Is`. Ensure `Closure(None)` (generic check) correctly matches specialized types like `Closure(Some(C))` (constant closure). Using `.remove_constant()` on types before comparison is crucial in `ssa_optimizer.rs`.

## JIT Optimization Logic

- **`propagate_types` Pass**: Analyzes dataflow to identify constant closures (`Closure(Some(C))`). Logic in `src/ir_processor/propagate_types.rs`.
- **Specialization**: `jit_func.rs` uses available type info (from `locals`) to unbox closure entries or arguments.
- **Constant Folding**: `ssa_optimizer.rs` folds constants. Be careful with strict equality checks on specialized types.
70 changes: 70 additions & 0 deletions webschembly-compiler-crates/ir/src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,73 @@ impl fmt::Display for Display<'_, JitBasicBlockId> {
Ok(())
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, From, Into)]
pub struct ClosureEnvIndex(pub usize);

impl ClosureEnvIndex {
pub fn display<'a>(&self, meta: &'a Meta) -> Display<'a, ClosureEnvIndex> {
Display { value: *self, meta }
}
}

impl fmt::Display for Display<'_, ClosureEnvIndex> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "closure_env{}", self.value.0)
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, From, Into)]
pub struct ClosureArgIndex(pub usize);

impl ClosureArgIndex {
pub fn display<'a>(&self, meta: &'a Meta) -> Display<'a, ClosureArgIndex> {
Display { value: *self, meta }
}
}

impl fmt::Display for Display<'_, ClosureArgIndex> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "closure_arg{}", self.value.0)
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, From, Into)]
pub struct BBIndex(pub usize);

impl BBIndex {
pub fn display<'a>(&self, meta: &'a Meta) -> Display<'a, BBIndex> {
Display { value: *self, meta }
}
}

impl fmt::Display for Display<'_, BBIndex> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "bb_index{}", self.value.0)
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ConstantClosure {
pub module_id: JitModuleId,
pub func_id: JitFuncId,
pub env_index: ClosureEnvIndex,
}

impl ConstantClosure {
pub fn display<'a>(&self, meta: &'a Meta) -> Display<'a, ConstantClosure> {
Display { value: *self, meta }
}
}

impl fmt::Display for Display<'_, ConstantClosure> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"constant_closure({}, {}, {})",
self.value.module_id.display(self.meta),
self.value.func_id.display(self.meta),
self.value.env_index.display(self.meta)
)
}
}
28 changes: 27 additions & 1 deletion webschembly-compiler-crates/ir/src/typ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ TypeもしくはmutableなTypeを表す
Type自体にRefを含めて再帰的にしてしまうと無限種類の型を作れるようになってしまうので、IRではそれを避けるためこのような構造になっている
TODO: LocalTypeという名前は適切ではない
*/
use crate::id::ConstantClosure;

#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, derive_more::Display)]
pub enum LocalType {
#[display("ref<{}>", _0)]
Expand Down Expand Up @@ -38,6 +40,14 @@ impl LocalType {
_ => None,
}
}

pub fn remove_constant(self) -> Self {
match self {
LocalType::Ref(typ) => LocalType::Ref(typ.remove_constant()),
LocalType::Type(typ) => LocalType::Type(typ.remove_constant()),
_ => self,
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, derive_more::Display)]
Expand All @@ -55,6 +65,13 @@ impl Type {
Type::Obj => None,
}
}

pub fn remove_constant(self) -> Self {
match self {
Type::Val(val_type) => Type::Val(val_type.remove_constant()),
Type::Obj => Type::Obj,
}
}
}

impl From<ValType> for Type {
Expand Down Expand Up @@ -88,7 +105,16 @@ pub enum ValType {
#[display("uvector<{0}>", _0)]
UVector(UVectorKind),
#[display("closure")]
Closure,
Closure(Option<ConstantClosure>),
}

impl ValType {
pub fn remove_constant(self) -> Self {
match self {
ValType::Closure(_) => ValType::Closure(None),
_ => self,
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, derive_more::Display)]
Expand Down
32 changes: 15 additions & 17 deletions webschembly-compiler/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ use crate::ir_processor::optimizer::remove_unused_local;
use crate::ir_processor::register_allocation::register_allocation;
use crate::ir_processor::ssa::split_critical_edges;
use crate::ir_processor::ssa::{debug_assert_ssa, remove_phi};
use crate::ir_processor::ssa_optimizer::ModuleInliner;
use crate::ir_processor::ssa_optimizer::SsaOptimizerConfig;
use crate::ir_processor::ssa_optimizer::inlining;
use crate::ir_processor::ssa_optimizer::ssa_optimize;
use crate::jit::{Jit, JitConfig};
use crate::lexer;
Expand Down Expand Up @@ -133,8 +131,8 @@ impl Compiler {
&mut self.global_manager,
module_id,
func_id,
crate::jit::env_index_manager::EnvIndex(env_index),
crate::jit::closure_global_layout::ClosureIndex(func_index),
ir::ClosureEnvIndex(env_index),
ir::ClosureArgIndex(func_index),
);

preprocess_module(&mut module);
Expand Down Expand Up @@ -167,10 +165,10 @@ impl Compiler {
let mut module = jit.instantiate_bb(
module_id,
func_id,
crate::jit::env_index_manager::EnvIndex(env_index),
crate::jit::closure_global_layout::ClosureIndex(func_index),
ir::ClosureEnvIndex(env_index),
ir::ClosureArgIndex(func_index),
bb_id,
crate::jit::bb_index_manager::BBIndex(index),
ir::BBIndex(index),
&mut self.global_manager,
);
preprocess_module(&mut module);
Expand Down Expand Up @@ -211,12 +209,12 @@ impl Compiler {
&mut self.global_manager,
module_id,
func_id,
crate::jit::env_index_manager::EnvIndex(env_index),
crate::jit::closure_global_layout::ClosureIndex(func_index),
ir::ClosureEnvIndex(env_index),
ir::ClosureArgIndex(func_index),
bb_id,
kind,
ir::BasicBlockId::from(source_bb_id),
crate::jit::bb_index_manager::BBIndex(source_index),
ir::BBIndex(source_index),
)
.map(|mut module| {
preprocess_module(&mut module);
Expand All @@ -243,13 +241,8 @@ fn preprocess_module(module: &mut ir::Module) {
}

fn optimize_module(module: &mut ir::Module, config: SsaOptimizerConfig) {
let mut module_inliner = ModuleInliner::new(module);
let n = 5;
for i in 0..n {
if config.enable_inlining {
// inliningはInstrKind::Closureのfunc_idに依存しているので、JIT後のモジュールには使えない
inlining(module, &mut module_inliner, i == n - 1);
}
let n = 10;
for _ in 0..n {
for func in module.funcs.values_mut() {
ssa_optimize(
func,
Expand All @@ -259,6 +252,9 @@ fn optimize_module(module: &mut ir::Module, config: SsaOptimizerConfig) {
},
);
}
if config.enable_inlining {
crate::ir_processor::inline::inline_module(module);
}
}
}

Expand All @@ -273,6 +269,8 @@ fn postprocess(module: &mut ir::Module, global_manager: &mut GlobalManager) {
register_allocation(func);

remove_unused_local(func);

crate::ir_processor::remove_constant::remove_constant(func);
}

// モジュールごとにグローバルを真面目に管理するのは大変なのでここで計算
Expand Down
20 changes: 13 additions & 7 deletions webschembly-compiler/src/ir_generator/module_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ impl<'a, 'b> FuncGenerator<'a, 'b> {
let bb_entry = self.builder.bbs.allocate_key();
self.builder.current_bb_id = Some(bb_entry);

let self_closure = self.builder.local(Type::Val(ValType::Closure));
let self_closure = self.builder.local(Type::Val(ValType::Closure(None)));
let args = self.builder.local(LocalType::VariadicArgs);
let args_len_local = self.builder.local(Type::Val(ValType::Int));
let expected_args_len_local = self.builder.local(Type::Val(ValType::Int));
Expand Down Expand Up @@ -559,7 +559,13 @@ impl<'a, 'b> FuncGenerator<'a, 'b> {
ast::Expr::Lambda(x, lambda) => {
let func_id = self.module_generator.gen_func(x, ast.span, lambda);
let func_local = self.builder.local(LocalType::FuncRef);
let val_type_local = self.builder.local(Type::Val(ValType::Closure));
let val_type_local =
self.builder
.local(Type::Val(ValType::Closure(Some(ConstantClosure {
module_id: self.module_generator.id,
func_id: JitFuncId::from(func_id),
env_index: ClosureEnvIndex(0),
}))));
self.builder.exprs.push(Instr {
local: Some(func_local),
kind: InstrKind::FuncRef(func_id),
Expand Down Expand Up @@ -617,7 +623,7 @@ impl<'a, 'b> FuncGenerator<'a, 'b> {
});
self.builder.exprs.push(Instr {
local: result,
kind: InstrKind::ToObj(ValType::Closure, val_type_local),
kind: InstrKind::ToObj(ValType::Closure(None), val_type_local),
});
}
ast::Expr::If(_, ast::If { cond, then, els }) => {
Expand Down Expand Up @@ -887,7 +893,7 @@ impl<'a, 'b> FuncGenerator<'a, 'b> {
let is_closure_local = self.builder.local(Type::Val(ValType::Bool));
self.builder.exprs.push(Instr {
local: Some(is_closure_local),
kind: InstrKind::Is(ValType::Closure, obj_func_local),
kind: InstrKind::Is(ValType::Closure(None), obj_func_local),
});

let then_bb_id = self.builder.bbs.allocate_key();
Expand All @@ -912,10 +918,10 @@ impl<'a, 'b> FuncGenerator<'a, 'b> {

self.builder.current_bb_id = Some(then_bb_id);

let closure_local = self.builder.local(ValType::Closure);
let closure_local = self.builder.local(ValType::Closure(None));
self.builder.exprs.push(Instr {
local: Some(closure_local),
kind: InstrKind::FromObj(ValType::Closure, obj_func_local),
kind: InstrKind::FromObj(ValType::Closure(None), obj_func_local),
});

let args_local = self.builder.local(LocalType::VariadicArgs);
Expand Down Expand Up @@ -1449,7 +1455,7 @@ impl BuiltinConversionRule {
ir_gen: |ctx, arg1| {
ctx.builder.exprs.push(Instr {
local: Some(ctx.dest),
kind: InstrKind::Is(ValType::Closure, arg1),
kind: InstrKind::Is(ValType::Closure(None), arg1),
});
},
}],
Expand Down
2 changes: 1 addition & 1 deletion webschembly-compiler/src/ir_processor/desugar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ fn desugar_call_closure(
func_type: FuncType {
args: {
let mut args = Vec::new();
args.push(ValType::Closure.into());
args.push(ValType::Closure(None).into());
args.extend(call_closure.arg_types);
args
},
Expand Down
Loading
Loading