diff --git a/src/backend/mir_interpreter/handlers/boxes_map.rs b/src/backend/mir_interpreter/handlers/boxes_map.rs index c5a0bf04..2557a0fb 100644 --- a/src/backend/mir_interpreter/handlers/boxes_map.rs +++ b/src/backend/mir_interpreter/handlers/boxes_map.rs @@ -30,7 +30,13 @@ pub(super) fn try_handle_map_box( "MapBox.getField expects 1 arg".into(), )); } - let k = this.reg_load(args[0])?.to_nyash_box(); + let k_vm = this.reg_load(args[0])?; + // Field access expects a String key; otherwise return a stable tag. + if !matches!(k_vm, VMValue::String(_)) { + if let Some(d) = dst { this.regs.insert(d, VMValue::String("[map/bad-key] field name must be string".to_string())); } + return Ok(true); + } + let k = k_vm.to_nyash_box(); let ret = mb.get(k); if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(ret)); @@ -43,7 +49,12 @@ pub(super) fn try_handle_map_box( "MapBox.setField expects 2 args".into(), )); } - let k = this.reg_load(args[0])?.to_nyash_box(); + let k_vm = this.reg_load(args[0])?; + if !matches!(k_vm, VMValue::String(_)) { + if let Some(d) = dst { this.regs.insert(d, VMValue::String("[map/bad-key] field name must be string".to_string())); } + return Ok(true); + } + let k = k_vm.to_nyash_box(); let v = this.reg_load(args[1])?.to_nyash_box(); let ret = mb.set(k, v); if let Some(d) = dst { diff --git a/src/backend/mir_interpreter/handlers/boxes_object_fields.rs b/src/backend/mir_interpreter/handlers/boxes_object_fields.rs index c34a870d..27ec72ad 100644 --- a/src/backend/mir_interpreter/handlers/boxes_object_fields.rs +++ b/src/backend/mir_interpreter/handlers/boxes_object_fields.rs @@ -40,6 +40,24 @@ pub(super) fn try_handle_object_fields( if args.len() != 1 { return Err(VMError::InvalidInstruction("getField expects 1 arg".into())); } + // MapBox special-case: bridge to MapBox.get, with string-only key + if let Ok(VMValue::BoxRef(bref)) = this.reg_load(box_val) { + if bref.as_any().downcast_ref::().is_some() { + let key_vm = this.reg_load(args[0])?; + if let VMValue::String(_) = key_vm { + let k = key_vm.to_nyash_box(); + let map = bref.share_box(); + if let Some(mb) = map.as_any().downcast_ref::() { + let ret = mb.get(k); + if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(ret)); } + return Ok(true); + } + } else { + if let Some(d) = dst { this.regs.insert(d, VMValue::String("[map/bad-key] field name must be string".to_string())); } + return Ok(true); + } + } + } if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { let rk = match this.reg_load(box_val) { Ok(VMValue::BoxRef(ref b)) => format!("BoxRef({})", b.type_name()), @@ -282,6 +300,25 @@ pub(super) fn try_handle_object_fields( "setField expects 2 args".into(), )); } + // MapBox special-case: bridge to MapBox.set, with string-only key + if let Ok(VMValue::BoxRef(bref)) = this.reg_load(box_val) { + if bref.as_any().downcast_ref::().is_some() { + let key_vm = this.reg_load(args[0])?; + if let VMValue::String(_) = key_vm { + let k = key_vm.to_nyash_box(); + let v = this.reg_load(args[1])?.to_nyash_box(); + let map = bref.share_box(); + if let Some(mb) = map.as_any().downcast_ref::() { + let _ = mb.set(k, v); + if let Some(d) = dst { this.regs.insert(d, VMValue::Void); } + return Ok(true); + } + } else { + if let Some(d) = dst { this.regs.insert(d, VMValue::String("[map/bad-key] field name must be string".to_string())); } + return Ok(true); + } + } + } let fname = match this.reg_load(args[0])? { VMValue::String(s) => s, v => v.to_string(), diff --git a/tools/smokes/v2/README.md b/tools/smokes/v2/README.md index a1607f95..6f6f6566 100644 --- a/tools/smokes/v2/README.md +++ b/tools/smokes/v2/README.md @@ -82,6 +82,18 @@ tools/smokes/v2/ - `SMOKES_ENABLE_CORE_CANARY=1` — Core interpreter canaries(emit→nyvm/core, Gate‑C Core)。 - Stage‑B canaries are default‑ON in quick (`core/stageb/*`). - Selfhost Stage‑B helpers under `core/selfhost_stageb_*` remain opt‑in for dev. + +Bridge canonicalize (diff canaries) +- 目的: v1 JSON の ModuleFunction を Method へ決定的に正規化することを保証する。 +- ON/OFF/FAIL 規約: + - ON: `HAKO_BRIDGE_INJECT_SINGLETON=1` で mutated JSON(dump)が生成され、`callee.type": "Method"` へ書き換わる。 + - OFF: 変異しない(dumpが生成されない or `ModuleFunction` のまま)。 + - FAIL: 無効な JSON/未対応のcallee などは Fail‑Fast(stderrに安定文言)。 +- 常時テスト(quick/core/bridge): + - canonicalize_diff_on_off_vm.sh(LLVMPhiInstructionBox.lower_phi ベース) + - canonicalize_array_len_on_off_vm.sh(ArrayBox.len → Method(ArrayBox.size)) + - canonicalize_map_len_on_off_vm.sh(MapBox.len → Method(MapBox.len)) + - canonicalize_static_lower_*(binop/compare/branch/jump/return) - `SMOKES_ENABLE_STAGEB_V1=1` — Stage‑B v1 互換カナリア(`selfhost_stageb_v1_compat_vm.sh`)。未配線時は SKIP。 ## 🔧 テスト作成規約 diff --git a/tools/smokes/v2/profiles/quick/core/bridge/canonicalize_static_lower_branch_on_off_vm.sh b/tools/smokes/v2/profiles/quick/core/bridge/canonicalize_static_lower_branch_on_off_vm.sh new file mode 100644 index 00000000..71db3ea5 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/bridge/canonicalize_static_lower_branch_on_off_vm.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# canonicalize_static_lower_branch_on_off_vm.sh — Verify LLVMBranchInstructionBox.lower_branch rewrite + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then + ROOT="$ROOT_GIT" +else + ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)" +fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh" +require_env || exit 2 + +json_path="/tmp/ny_v1_lower_branch_$$.json" +mut_on="/tmp/ny_v1_lower_branch_on_$$.json" +mut_off="/tmp/ny_v1_lower_branch_off_$$.json" + +cat >"$json_path" <<'JSON' +{"schema_version":"1.0","functions":[{"name":"main","blocks":[{"id":0,"instructions":[{"op":"mir_call","mir_call":{"callee":{"type":"ModuleFunction","name":"LLVMBranchInstructionBox.lower_branch"},"args":[1]}},{"op":"ret"}]}]}]} +JSON + +set +e +HAKO_NYVM_V1_DOWNCONVERT=1 HAKO_BRIDGE_INJECT_SINGLETON=1 HAKO_DEBUG_NYVM_BRIDGE_DUMP_MUT="$mut_on" \ + "$ROOT/target/release/nyash" --json-file "$json_path" >/dev/null 2>&1 +set -e || true +if [ ! -f "$mut_on" ] || ! grep -q '"type":"Method"' "$mut_on" || ! grep -q '"box_name":"LLVMBranchInstructionBox"' "$mut_on" || ! grep -q '"method":"lower_branch"' "$mut_on"; then + echo "[FAIL] canonicalize_static_lower_branch_on_off_vm (ON)" >&2; exit 1 +fi + +set +e +HAKO_NYVM_V1_DOWNCONVERT=1 HAKO_DEBUG_NYVM_BRIDGE_DUMP_MUT="$mut_off" \ + "$ROOT/target/release/nyash" --json-file "$json_path" >/dev/null 2>&1 +set -e || true +if [ -f "$mut_off" ] && ! grep -q '"type":"ModuleFunction"' "$mut_off"; then + echo "[FAIL] canonicalize_static_lower_branch_on_off_vm (OFF)" >&2; exit 1 +fi + +echo "[PASS] canonicalize_static_lower_branch_on_off_vm" +rm -f "$json_path" "$mut_on" "$mut_off" +exit 0 + diff --git a/tools/smokes/v2/profiles/quick/core/bridge/canonicalize_static_lower_compare_on_off_vm.sh b/tools/smokes/v2/profiles/quick/core/bridge/canonicalize_static_lower_compare_on_off_vm.sh new file mode 100644 index 00000000..5e6a4a66 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/bridge/canonicalize_static_lower_compare_on_off_vm.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# canonicalize_static_lower_compare_on_off_vm.sh — Verify LLVMCompareInstructionBox.lower_compare rewrite + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then + ROOT="$ROOT_GIT" +else + ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)" +fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh" +require_env || exit 2 + +json_path="/tmp/ny_v1_lower_compare_$$.json" +mut_on="/tmp/ny_v1_lower_compare_on_$$.json" +mut_off="/tmp/ny_v1_lower_compare_off_$$.json" + +cat >"$json_path" <<'JSON' +{"schema_version":"1.0","functions":[{"name":"main","blocks":[{"id":0,"instructions":[{"op":"mir_call","mir_call":{"callee":{"type":"ModuleFunction","name":"LLVMCompareInstructionBox.lower_compare"},"args":[1,2]}},{"op":"ret"}]}]}]} +JSON + +set +e +HAKO_NYVM_V1_DOWNCONVERT=1 HAKO_BRIDGE_INJECT_SINGLETON=1 HAKO_DEBUG_NYVM_BRIDGE_DUMP_MUT="$mut_on" \ + "$ROOT/target/release/nyash" --json-file "$json_path" >/dev/null 2>&1 +set -e || true +if [ ! -f "$mut_on" ] || ! grep -q '"type":"Method"' "$mut_on" || ! grep -q '"box_name":"LLVMCompareInstructionBox"' "$mut_on" || ! grep -q '"method":"lower_compare"' "$mut_on"; then + echo "[FAIL] canonicalize_static_lower_compare_on_off_vm (ON)" >&2; exit 1 +fi + +set +e +HAKO_NYVM_V1_DOWNCONVERT=1 HAKO_DEBUG_NYVM_BRIDGE_DUMP_MUT="$mut_off" \ + "$ROOT/target/release/nyash" --json-file "$json_path" >/dev/null 2>&1 +set -e || true +if [ -f "$mut_off" ] && ! grep -q '"type":"ModuleFunction"' "$mut_off"; then + echo "[FAIL] canonicalize_static_lower_compare_on_off_vm (OFF)" >&2; exit 1 +fi + +echo "[PASS] canonicalize_static_lower_compare_on_off_vm" +rm -f "$json_path" "$mut_on" "$mut_off" +exit 0 + diff --git a/tools/smokes/v2/profiles/quick/core/bridge/canonicalize_static_lower_jump_on_off_vm.sh b/tools/smokes/v2/profiles/quick/core/bridge/canonicalize_static_lower_jump_on_off_vm.sh new file mode 100644 index 00000000..d2e72496 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/bridge/canonicalize_static_lower_jump_on_off_vm.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# canonicalize_static_lower_jump_on_off_vm.sh — Verify LLVMJumpInstructionBox.lower_jump rewrite + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then + ROOT="$ROOT_GIT" +else + ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)" +fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh" +require_env || exit 2 + +json_path="/tmp/ny_v1_lower_jump_$$.json" +mut_on="/tmp/ny_v1_lower_jump_on_$$.json" +mut_off="/tmp/ny_v1_lower_jump_off_$$.json" + +cat >"$json_path" <<'JSON' +{"schema_version":"1.0","functions":[{"name":"main","blocks":[{"id":0,"instructions":[{"op":"mir_call","mir_call":{"callee":{"type":"ModuleFunction","name":"LLVMJumpInstructionBox.lower_jump"},"args":[1]}},{"op":"ret"}]}]}]} +JSON + +set +e +HAKO_NYVM_V1_DOWNCONVERT=1 HAKO_BRIDGE_INJECT_SINGLETON=1 HAKO_DEBUG_NYVM_BRIDGE_DUMP_MUT="$mut_on" \ + "$ROOT/target/release/nyash" --json-file "$json_path" >/dev/null 2>&1 +set -e || true +if [ ! -f "$mut_on" ] || ! grep -q '"type":"Method"' "$mut_on" || ! grep -q '"box_name":"LLVMJumpInstructionBox"' "$mut_on" || ! grep -q '"method":"lower_jump"' "$mut_on"; then + echo "[FAIL] canonicalize_static_lower_jump_on_off_vm (ON)" >&2; exit 1 +fi + +set +e +HAKO_NYVM_V1_DOWNCONVERT=1 HAKO_DEBUG_NYVM_BRIDGE_DUMP_MUT="$mut_off" \ + "$ROOT/target/release/nyash" --json-file "$json_path" >/dev/null 2>&1 +set -e || true +if [ -f "$mut_off" ] && ! grep -q '"type":"ModuleFunction"' "$mut_off"; then + echo "[FAIL] canonicalize_static_lower_jump_on_off_vm (OFF)" >&2; exit 1 +fi + +echo "[PASS] canonicalize_static_lower_jump_on_off_vm" +rm -f "$json_path" "$mut_on" "$mut_off" +exit 0 + diff --git a/tools/smokes/v2/profiles/quick/core/map/map_bad_key_field_vm.sh b/tools/smokes/v2/profiles/quick/core/map/map_bad_key_field_vm.sh new file mode 100644 index 00000000..1f9f5298 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/map/map_bad_key_field_vm.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# map_bad_key_field_vm.sh — Map.getField/ setField with non-string key returns [map/bad-key] + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then + ROOT="$ROOT_GIT" +else + ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)" +fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh" +require_env || exit 2 + +code='static box Main { main() { local m=new MapBox(); print(m.getField(123)); m.setField(456, 1); return 0 } }' +out=$(run_nyash_vm -c "$code") +if echo "$out" | grep -q "\[map/bad-key\]"; then + echo "[PASS] map_bad_key_field_vm" + exit 0 +else + echo "[FAIL] map_bad_key_field_vm" >&2 + echo "--- output ---" >&2 + echo "$out" >&2 + exit 1 +fi +