Bridge canonicalize: add compare/branch/jump diff tests; Map field bad-key stable tag with smoke; Update smokes README with diff canary policy

This commit is contained in:
nyash-codex
2025-11-02 12:00:06 +09:00
parent c81dc20d5c
commit 1a5b269f8d
7 changed files with 214 additions and 2 deletions

View File

@ -30,7 +30,13 @@ pub(super) fn try_handle_map_box(
"MapBox.getField expects 1 arg".into(), "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); let ret = mb.get(k);
if let Some(d) = dst { if let Some(d) = dst {
this.regs.insert(d, VMValue::from_nyash_box(ret)); 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(), "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 v = this.reg_load(args[1])?.to_nyash_box();
let ret = mb.set(k, v); let ret = mb.set(k, v);
if let Some(d) = dst { if let Some(d) = dst {

View File

@ -40,6 +40,24 @@ pub(super) fn try_handle_object_fields(
if args.len() != 1 { if args.len() != 1 {
return Err(VMError::InvalidInstruction("getField expects 1 arg".into())); 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::<crate::boxes::map_box::MapBox>().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::<crate::boxes::map_box::MapBox>() {
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") { if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
let rk = match this.reg_load(box_val) { let rk = match this.reg_load(box_val) {
Ok(VMValue::BoxRef(ref b)) => format!("BoxRef({})", b.type_name()), 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(), "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::<crate::boxes::map_box::MapBox>().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::<crate::boxes::map_box::MapBox>() {
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])? { let fname = match this.reg_load(args[0])? {
VMValue::String(s) => s, VMValue::String(s) => s,
v => v.to_string(), v => v.to_string(),

View File

@ -82,6 +82,18 @@ tools/smokes/v2/
- `SMOKES_ENABLE_CORE_CANARY=1` — Core interpreter canariesemit→nyvm/core, GateC Core - `SMOKES_ENABLE_CORE_CANARY=1` — Core interpreter canariesemit→nyvm/core, GateC Core
- StageB canaries are defaultON in quick (`core/stageb/*`). - StageB canaries are defaultON in quick (`core/stageb/*`).
- Selfhost StageB helpers under `core/selfhost_stageb_*` remain optin for dev. - Selfhost StageB helpers under `core/selfhost_stageb_*` remain optin for dev.
Bridge canonicalize (diff canaries)
- 目的: v1 JSON の ModuleFunction を Method へ決定的に正規化することを保証する。
- ON/OFF/FAIL 規約:
- ON: `HAKO_BRIDGE_INJECT_SINGLETON=1` で mutated JSONdumpが生成され、`callee.type": "Method"` へ書き換わる。
- OFF: 変異しないdumpが生成されない or `ModuleFunction` のまま)。
- FAIL: 無効な JSON/未対応のcallee などは FailFaststderrに安定文言
- 常時テストquick/core/bridge:
- canonicalize_diff_on_off_vm.shLLVMPhiInstructionBox.lower_phi ベース)
- canonicalize_array_len_on_off_vm.shArrayBox.len → Method(ArrayBox.size)
- canonicalize_map_len_on_off_vm.shMapBox.len → Method(MapBox.len)
- canonicalize_static_lower_*binop/compare/branch/jump/return
- `SMOKES_ENABLE_STAGEB_V1=1` — StageB v1 互換カナリア(`selfhost_stageb_v1_compat_vm.sh`)。未配線時は SKIP。 - `SMOKES_ENABLE_STAGEB_V1=1` — StageB v1 互換カナリア(`selfhost_stageb_v1_compat_vm.sh`)。未配線時は SKIP。
## 🔧 テスト作成規約 ## 🔧 テスト作成規約

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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