feat(llvm): Phase 131-15 - print/concat segfault 根治修正
## P1-1/P1-2: TypeFacts 優先化 - binop.py: operand facts が dst_type ヒントより優先 - mir_json_emit.rs: Unknown 時に dst_type を出さない - function_lower.py: value_types を metadata から読み込み ## P2: handle concat 統一(根治) - print シグネチャ修正: i64(i64) → void(i8*) - Mixed concat を handle ベースに統一: - concat_si/concat_is → concat_hh - box.from_i64 で integer を handle 化 - Everything is Box 哲学に統一 - legacy 関数は互換性のために保持 ## 結果 - ✅ print("Result: " + 3) → Result: 3 - ✅ segfault 解消 - ✅ Everything is Box 統一 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -119,7 +119,6 @@ pub extern "C" fn nyash_string_concat_hh_export(a_h: i64, b_h: i64) -> i64 {
|
|||||||
nyash_rust::runtime::global_hooks::gc_alloc(s.len() as u64);
|
nyash_rust::runtime::global_hooks::gc_alloc(s.len() as u64);
|
||||||
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(StringBox::new(s));
|
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(StringBox::new(s));
|
||||||
let h = handles::to_handle_arc(arc) as i64;
|
let h = handles::to_handle_arc(arc) as i64;
|
||||||
eprintln!("[TRACE] concat_hh -> {}", h);
|
|
||||||
h
|
h
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +238,6 @@ pub extern "C" fn nyash_box_from_i8_string(ptr: *const i8) -> i64 {
|
|||||||
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(StringBox::new(s.clone()));
|
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(StringBox::new(s.clone()));
|
||||||
nyash_rust::runtime::global_hooks::gc_alloc(s.len() as u64);
|
nyash_rust::runtime::global_hooks::gc_alloc(s.len() as u64);
|
||||||
let h = handles::to_handle_arc(arc) as i64;
|
let h = handles::to_handle_arc(arc) as i64;
|
||||||
eprintln!("[TRACE] from_i8_string -> {}", h);
|
|
||||||
h
|
h
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,7 +261,8 @@ pub extern "C" fn nyash_box_from_i64(val: i64) -> i64 {
|
|||||||
};
|
};
|
||||||
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(IntegerBox::new(val));
|
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(IntegerBox::new(val));
|
||||||
nyash_rust::runtime::global_hooks::gc_alloc(8);
|
nyash_rust::runtime::global_hooks::gc_alloc(8);
|
||||||
handles::to_handle_arc(arc) as i64
|
let h = handles::to_handle_arc(arc) as i64;
|
||||||
|
h
|
||||||
}
|
}
|
||||||
|
|
||||||
// integer.get_h(handle) -> i64
|
// integer.get_h(handle) -> i64
|
||||||
|
|||||||
@ -44,6 +44,8 @@ pub extern "C" fn nyash_string_concat_ss(a: *const i8, b: *const i8) -> *mut i8
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exported as: nyash.string.concat_si(i8* a, i64 b) -> i8*
|
// Exported as: nyash.string.concat_si(i8* a, i64 b) -> i8*
|
||||||
|
// NOTE: Phase 131-15-P1 - Kept for diagnostic/compatibility purposes
|
||||||
|
// LLVM backend now uses concat_hh(handle, handle) for all mixed concatenations
|
||||||
#[export_name = "nyash.string.concat_si"]
|
#[export_name = "nyash.string.concat_si"]
|
||||||
pub extern "C" fn nyash_string_concat_si(a: *const i8, b: i64) -> *mut i8 {
|
pub extern "C" fn nyash_string_concat_si(a: *const i8, b: i64) -> *mut i8 {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
@ -63,6 +65,8 @@ pub extern "C" fn nyash_string_concat_si(a: *const i8, b: i64) -> *mut i8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exported as: nyash.string.concat_is(i64 a, i8* b) -> i8*
|
// Exported as: nyash.string.concat_is(i64 a, i8* b) -> i8*
|
||||||
|
// NOTE: Phase 131-15-P1 - Kept for diagnostic/compatibility purposes
|
||||||
|
// LLVM backend now uses concat_hh(handle, handle) for all mixed concatenations
|
||||||
#[export_name = "nyash.string.concat_is"]
|
#[export_name = "nyash.string.concat_is"]
|
||||||
pub extern "C" fn nyash_string_concat_is(a: i64, b: *const i8) -> *mut i8 {
|
pub extern "C" fn nyash_string_concat_is(a: i64, b: *const i8) -> *mut i8 {
|
||||||
let mut s = a.to_string();
|
let mut s = a.to_string();
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
# Phase 131-15: print/concat segfault (LLVM harness)
|
||||||
|
|
||||||
|
Status: Active
|
||||||
|
Scope: Case C が “ループ自体は動くが、print/concat で segfault” する問題の切り分け。
|
||||||
|
Related:
|
||||||
|
- SSOT (LLVM棚卸し): `docs/development/current/main/phase131-3-llvm-lowering-inventory.md`
|
||||||
|
- ENV: `docs/reference/environment-variables.md`(`NYASH_LLVM_DUMP_IR`, `NYASH_LLVM_STRICT`, `NYASH_LLVM_TRACE_*`)
|
||||||
|
|
||||||
|
## 状態
|
||||||
|
|
||||||
|
- ループ本体(counter 更新/比較/break)は進む(例: `1,2` までは観測できる)。
|
||||||
|
- `print("Result: " + counter)` のような “concat + print” 経路で segfault。
|
||||||
|
|
||||||
|
## 切り分け(最優先)
|
||||||
|
|
||||||
|
同じ backend(LLVM harness)で、以下の3つを順に試す:
|
||||||
|
|
||||||
|
1. `return counter`(print なし)
|
||||||
|
2. `print(counter)`(concat なし)
|
||||||
|
3. `print("Result: " + counter)`(concat + print)
|
||||||
|
|
||||||
|
狙い:
|
||||||
|
- 1 と 2 が通って 3 だけ落ちるなら concat/coercion が本命。
|
||||||
|
- 2 で落ちるなら console 呼び出し ABI / routing が本命。
|
||||||
|
|
||||||
|
## 典型原因クラス
|
||||||
|
|
||||||
|
- **ABI routing の誤り**: `nyash.console.log(i8*)` に i64(handle/int) を渡している、またはその逆。
|
||||||
|
- **String coercion の誤り**: `to_i8p_h` / `concat_*` の引数が “handle と integer” で混線している。
|
||||||
|
- **dst_type hint の解釈違い**: MIR JSON の `dst_type` が Python 側で誤った関数選択に使われている。
|
||||||
|
|
||||||
|
## 観測手順
|
||||||
|
|
||||||
|
- LLVM IR dump:
|
||||||
|
- `NYASH_LLVM_DUMP_IR=/tmp/case_c.ll tools/build_llvm.sh apps/tests/llvm_stage3_loop_only.hako -o /tmp/case_c`
|
||||||
|
- strict + traces:
|
||||||
|
- `NYASH_LLVM_STRICT=1 NYASH_LLVM_TRACE_VALUES=1 NYASH_LLVM_TRACE_PHI=1 NYASH_LLVM_TRACE_OUT=/tmp/case_c.trace ...`
|
||||||
|
|
||||||
|
IR で見るポイント:
|
||||||
|
- concat 直前の値の型/変換:
|
||||||
|
- `nyash.string.to_i8p_h(i64 <arg>)` の `<arg>` が StringBox handle か(整数を渡していないか)
|
||||||
|
- `nyash.string.concat_*` の引数型が意図と一致しているか
|
||||||
|
- print の呼び先:
|
||||||
|
- 文字列は `nyash.console.log(i8*)` か(または handle→string 変換済み)
|
||||||
|
- handle/int は `nyash.console.log_handle(i64)` か
|
||||||
|
|
||||||
|
## Done 条件
|
||||||
|
|
||||||
|
- 3つの切り分けケースが VM/LLVM で一致し、segfault が消える。
|
||||||
|
- `NYASH_LLVM_STRICT=1` でフォールバック無し(miss→0 など)が維持される。
|
||||||
|
|
||||||
@ -10,7 +10,7 @@
|
|||||||
| A | `apps/tests/phase87_llvm_exe_min.hako` | ✅ | ✅ | ✅ | **PASS** - Simple return 42, no BoxCall, exit code verified |
|
| A | `apps/tests/phase87_llvm_exe_min.hako` | ✅ | ✅ | ✅ | **PASS** - Simple return 42, no BoxCall, exit code verified |
|
||||||
| B | `apps/tests/loop_min_while.hako` | ✅ | ✅ | ✅ | **PASS** - Loop/PHI path runs end-to-end (Phase 131-10): prints `0,1,2` and exits |
|
| B | `apps/tests/loop_min_while.hako` | ✅ | ✅ | ✅ | **PASS** - Loop/PHI path runs end-to-end (Phase 131-10): prints `0,1,2` and exits |
|
||||||
| B2 | `/tmp/case_b_simple.hako` | ✅ | ✅ | ✅ | **PASS** - Simple print(42) without loop works |
|
| B2 | `/tmp/case_b_simple.hako` | ✅ | ✅ | ✅ | **PASS** - Simple print(42) without loop works |
|
||||||
| C | `apps/tests/llvm_stage3_loop_only.hako` | ✅ | ✅ | ⚠️ | **TAG-RUN** - Runs but result mismatch (VM ok / LLVM wrong) |
|
| C | `apps/tests/llvm_stage3_loop_only.hako` | ✅ | ✅ | ⚠️ | **TAG-RUN** - Loop ok; print/concat path segfaults |
|
||||||
|
|
||||||
## Root Causes Identified
|
## Root Causes Identified
|
||||||
|
|
||||||
@ -202,15 +202,18 @@ static box Main {
|
|||||||
- A loop-carrier PHI type-cycle bug was fixed by seeding the PHI type from the entry(init) value (Phase 131-11 H).
|
- A loop-carrier PHI type-cycle bug was fixed by seeding the PHI type from the entry(init) value (Phase 131-11 H).
|
||||||
- Root cause report: `docs/development/current/main/phase-131-11-g-phi-type-bug-report.md`
|
- Root cause report: `docs/development/current/main/phase-131-11-g-phi-type-bug-report.md`
|
||||||
|
|
||||||
**Current issue**: **TAG-RUN (wrong result)**
|
**Current issue**: **TAG-RUN (segfault in print/concat)**
|
||||||
VM and MIR look correct, but LLVM output does not match expected result for Case C.
|
Loop/carrier propagation is now consistent enough to run iterations, but the post-loop `print("Result: " + counter)` path segfaults.
|
||||||
|
|
||||||
**Next actions**:
|
**Next actions**:
|
||||||
- Dump LLVM IR (`NYASH_LLVM_DUMP_IR=...`) and trace PHI/value resolution (`NYASH_LLVM_TRACE_PHI=1`, `NYASH_LLVM_TRACE_VALUES=1`).
|
- Use strict mode + traces to catch the first wrong value/ABI boundary:
|
||||||
- Reduce Case C to isolate whether the bug is “loop value” or “string concat/print path”:
|
- `NYASH_LLVM_STRICT=1`, `NYASH_LLVM_TRACE_VALUES=1`, `NYASH_LLVM_TRACE_PHI=1`
|
||||||
- `return counter` (no string concat)
|
- Reduce to isolate the failing segment:
|
||||||
- `print(counter)` (no `"Result: " + ...`)
|
- `return counter` (no print)
|
||||||
- Compare with VM and inspect the IR use-sites.
|
- `print(counter)` (no concat)
|
||||||
|
- `print("Result: " + counter)` (concat + print)
|
||||||
|
- Dump IR around concat/externcall:
|
||||||
|
- `NYASH_LLVM_DUMP_IR=/tmp/case_c.ll`
|
||||||
|
|
||||||
**Update (Phase 131-13)**:
|
**Update (Phase 131-13)**:
|
||||||
- snapshot-only + strict resolver により、Case C の不一致が “LLVM の値解決バグ” ではなく
|
- snapshot-only + strict resolver により、Case C の不一致が “LLVM の値解決バグ” ではなく
|
||||||
|
|||||||
@ -121,6 +121,8 @@ NYASH_USE_STAGE1_CLI=1 STAGE1_EMIT_MIR_JSON=1 \
|
|||||||
| `NYASH_LLVM_TRACE_PHI=1` | OFF | PHI 配線/スナップショット解決の詳細トレース(Python backend) |
|
| `NYASH_LLVM_TRACE_PHI=1` | OFF | PHI 配線/スナップショット解決の詳細トレース(Python backend) |
|
||||||
| `NYASH_LLVM_TRACE_VALUES=1` | OFF | value 解決トレース(Python backend) |
|
| `NYASH_LLVM_TRACE_VALUES=1` | OFF | value 解決トレース(Python backend) |
|
||||||
| `NYASH_LLVM_TRACE_OUT=/tmp/llvm_trace.log` | unset | LLVM トレースの出力先(未指定なら stdout) |
|
| `NYASH_LLVM_TRACE_OUT=/tmp/llvm_trace.log` | unset | LLVM トレースの出力先(未指定なら stdout) |
|
||||||
|
| `NYASH_LLVM_STRICT=1` | OFF | Python LLVM backend を Fail-Fast モードにする(snapshot miss / use-before-def / PHI不整合 を即エラー化) |
|
||||||
|
| `NYASH_LLVM_PHI_STRICT=1` | OFF | PHI の default-zero フォールバックを禁止し、incoming miss を即エラー化 |
|
||||||
|
|
||||||
### 使用例
|
### 使用例
|
||||||
|
|
||||||
|
|||||||
@ -73,6 +73,22 @@ def lower_function(builder, func_data: Dict[str, Any]):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Phase 131-15-P1: Load value_types metadata from JSON into resolver
|
||||||
|
try:
|
||||||
|
metadata = func_data.get('metadata', {})
|
||||||
|
value_types_json = metadata.get('value_types', {})
|
||||||
|
# Convert string keys to integers and store in resolver
|
||||||
|
builder.resolver.value_types = {}
|
||||||
|
for vid_str, vtype in value_types_json.items():
|
||||||
|
try:
|
||||||
|
vid = int(vid_str)
|
||||||
|
builder.resolver.value_types[vid] = vtype
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
# If metadata loading fails, ensure value_types exists (empty fallback)
|
||||||
|
builder.resolver.value_types = {}
|
||||||
|
|
||||||
# Create or reuse function
|
# Create or reuse function
|
||||||
func = None
|
func = None
|
||||||
for f in builder.module.functions:
|
for f in builder.module.functions:
|
||||||
|
|||||||
@ -141,10 +141,49 @@ def lower_binop(
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# If explicit type hint is present, use it
|
# Phase 131-15-P1: TypeFacts > dst_type hint
|
||||||
if explicit_integer:
|
# Check operand facts before applying dst_type hint
|
||||||
|
operand_is_string = False
|
||||||
|
try:
|
||||||
|
# Check 1: string literals
|
||||||
|
if resolver is not None and hasattr(resolver, 'string_literals'):
|
||||||
|
if lhs in resolver.string_literals or rhs in resolver.string_literals:
|
||||||
|
operand_is_string = True
|
||||||
|
# Check 2: value_types
|
||||||
|
if not operand_is_string and resolver is not None and hasattr(resolver, 'value_types'):
|
||||||
|
lhs_type = resolver.value_types.get(lhs)
|
||||||
|
rhs_type = resolver.value_types.get(rhs)
|
||||||
|
if lhs_type and (lhs_type.get('kind') == 'string' or
|
||||||
|
(lhs_type.get('kind') == 'handle' and lhs_type.get('box_type') == 'StringBox')):
|
||||||
|
operand_is_string = True
|
||||||
|
if rhs_type and (rhs_type.get('kind') == 'string' or
|
||||||
|
(rhs_type.get('kind') == 'handle' and rhs_type.get('box_type') == 'StringBox')):
|
||||||
|
operand_is_string = True
|
||||||
|
# Check 3: pointer type
|
||||||
|
if not operand_is_string:
|
||||||
|
if lhs_raw is not None and hasattr(lhs_raw, 'type') and isinstance(lhs_raw.type, ir.PointerType):
|
||||||
|
operand_is_string = True
|
||||||
|
if rhs_raw is not None and hasattr(rhs_raw, 'type') and isinstance(rhs_raw.type, ir.PointerType):
|
||||||
|
operand_is_string = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Phase 131-15-P1: Operand facts take priority over dst_type hint
|
||||||
|
if operand_is_string:
|
||||||
|
# Operand is string: MUST use string concat
|
||||||
|
if explicit_integer and os.environ.get('NYASH_LLVM_STRICT') == '1':
|
||||||
|
# Fail-Fast in STRICT mode
|
||||||
|
raise RuntimeError(
|
||||||
|
f"[LLVM_PY/STRICT] Type conflict: dst_type=i64 but operand is string. "
|
||||||
|
f"lhs={lhs} rhs={rhs}"
|
||||||
|
)
|
||||||
|
# Force string concatenation when operand facts say so
|
||||||
|
is_str = True
|
||||||
|
elif explicit_integer:
|
||||||
|
# No string operands + explicit i64 hint: integer arithmetic
|
||||||
is_str = False
|
is_str = False
|
||||||
elif explicit_string:
|
elif explicit_string:
|
||||||
|
# Explicit string hint: string concat
|
||||||
is_str = True
|
is_str = True
|
||||||
else:
|
else:
|
||||||
# Phase 196: TypeFacts SSOT - Only check for actual string types (not use-site demands)
|
# Phase 196: TypeFacts SSOT - Only check for actual string types (not use-site demands)
|
||||||
@ -172,7 +211,11 @@ def lower_binop(
|
|||||||
if os.environ.get('NYASH_BINOP_DEBUG') == '1':
|
if os.environ.get('NYASH_BINOP_DEBUG') == '1':
|
||||||
print(f"[binop +] lhs={lhs} rhs={rhs} dst={dst}")
|
print(f"[binop +] lhs={lhs} rhs={rhs} dst={dst}")
|
||||||
print(f" dst_type={dst_type} explicit_integer={explicit_integer} explicit_string={explicit_string}")
|
print(f" dst_type={dst_type} explicit_integer={explicit_integer} explicit_string={explicit_string}")
|
||||||
print(f" is_ptr_side={is_ptr_side} any_tagged={any_tagged} is_str={is_str}")
|
print(f" operand_is_string={operand_is_string} is_ptr_side={is_ptr_side} is_str={is_str}")
|
||||||
|
if hasattr(resolver, 'value_types'):
|
||||||
|
lhs_vt = resolver.value_types.get(lhs)
|
||||||
|
rhs_vt = resolver.value_types.get(rhs)
|
||||||
|
print(f" value_types: lhs={lhs_vt} rhs={rhs_vt}")
|
||||||
if is_str:
|
if is_str:
|
||||||
# Helper: convert raw or resolved value to string handle
|
# Helper: convert raw or resolved value to string handle
|
||||||
def to_handle(raw, val, tag: str, vid: int):
|
def to_handle(raw, val, tag: str, vid: int):
|
||||||
@ -221,6 +264,9 @@ def lower_binop(
|
|||||||
rhs_tag = True
|
rhs_tag = True
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
# Phase 131-15-P1 DEBUG
|
||||||
|
if os.environ.get('NYASH_BINOP_DEBUG') == '1':
|
||||||
|
print(f" [concat path] lhs_tag={lhs_tag} rhs_tag={rhs_tag}")
|
||||||
if lhs_tag and rhs_tag:
|
if lhs_tag and rhs_tag:
|
||||||
# Both sides string-ish: concat_hh(handle, handle)
|
# Both sides string-ish: concat_hh(handle, handle)
|
||||||
hl = to_handle(lhs_raw, lhs_val, 'l', lhs)
|
hl = to_handle(lhs_raw, lhs_val, 'l', lhs)
|
||||||
@ -235,79 +281,72 @@ def lower_binop(
|
|||||||
res = builder.call(callee, [hl, hr], name=f"concat_hh_{dst}")
|
res = builder.call(callee, [hl, hr], name=f"concat_hh_{dst}")
|
||||||
safe_vmap_write(vmap, dst, res, "binop_concat_hh", resolver=resolver)
|
safe_vmap_write(vmap, dst, res, "binop_concat_hh", resolver=resolver)
|
||||||
else:
|
else:
|
||||||
# Mixed string + non-string (e.g., "len=" + 5). Use pointer concat helpers then box.
|
# Phase 131-15-P1: Mixed string + non-string (e.g., "len=" + 5)
|
||||||
|
# Root cause fix: Convert both to handles, then use concat_hh (no more concat_si/concat_is!)
|
||||||
i32 = ir.IntType(32); i8p = ir.IntType(8).as_pointer(); i64 = ir.IntType(64)
|
i32 = ir.IntType(32); i8p = ir.IntType(8).as_pointer(); i64 = ir.IntType(64)
|
||||||
# Helper: to i8* pointer for stringish side
|
|
||||||
def to_i8p_from_vid(vid: int, raw, val, tag: str):
|
# Helper: Convert any value to handle
|
||||||
# If raw is pointer-to-array: GEP
|
def to_handle(vid: int, raw, val, tag: str, is_string_tagged: bool):
|
||||||
|
# If raw is pointer-to-array (string literal): convert to handle via from_i8_string
|
||||||
if raw is not None and hasattr(raw, 'type') and isinstance(raw.type, ir.PointerType):
|
if raw is not None and hasattr(raw, 'type') and isinstance(raw.type, ir.PointerType):
|
||||||
try:
|
try:
|
||||||
if isinstance(raw.type.pointee, ir.ArrayType):
|
if isinstance(raw.type.pointee, ir.ArrayType):
|
||||||
c0 = ir.Constant(i32, 0)
|
c0 = ir.Constant(i32, 0)
|
||||||
return builder.gep(raw, [c0, c0], name=f"bin_gep_{tag}_{dst}")
|
ptr = builder.gep(raw, [c0, c0], name=f"bin_gep_{tag}_{dst}")
|
||||||
|
boxer = None
|
||||||
|
for f in builder.module.functions:
|
||||||
|
if f.name == 'nyash.box.from_i8_string':
|
||||||
|
boxer = f; break
|
||||||
|
if boxer is None:
|
||||||
|
boxer = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
|
||||||
|
return builder.call(boxer, [ptr], name=f"{tag}_str_h_{dst}")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
# If we have a string handle: call to_i8p_h
|
# If already i64: check if it's a handle (string-tagged) or raw integer
|
||||||
to_i8p = None
|
if val is not None and hasattr(val, 'type') and isinstance(val.type, ir.IntType) and val.type.width == 64:
|
||||||
|
if is_string_tagged:
|
||||||
|
# This side is string-tagged, so it's already a handle
|
||||||
|
return val
|
||||||
|
else:
|
||||||
|
# Not string-tagged: it's a raw integer, needs boxing
|
||||||
|
from_i64 = None
|
||||||
|
for f in builder.module.functions:
|
||||||
|
if f.name == 'nyash.box.from_i64':
|
||||||
|
from_i64 = f; break
|
||||||
|
if from_i64 is None:
|
||||||
|
from_i64 = ir.Function(builder.module, ir.FunctionType(i64, [i64]), name='nyash.box.from_i64')
|
||||||
|
return builder.call(from_i64, [val], name=f"{tag}_int_h_{dst}")
|
||||||
|
# Fallback: convert to i64 and box
|
||||||
|
i64_val = val
|
||||||
|
if i64_val is None:
|
||||||
|
i64_val = ir.Constant(i64, 0)
|
||||||
|
if hasattr(i64_val, 'type') and isinstance(i64_val.type, ir.PointerType):
|
||||||
|
i64_val = builder.ptrtoint(i64_val, i64, name=f"bin_p2i_{tag}_{dst}")
|
||||||
|
elif hasattr(i64_val, 'type') and isinstance(i64_val.type, ir.IntType) and i64_val.type.width != 64:
|
||||||
|
i64_val = builder.zext(i64_val, i64, name=f"bin_zext_{tag}_{dst}")
|
||||||
|
from_i64 = None
|
||||||
for f in builder.module.functions:
|
for f in builder.module.functions:
|
||||||
if f.name == 'nyash.string.to_i8p_h':
|
if f.name == 'nyash.box.from_i64':
|
||||||
to_i8p = f; break
|
from_i64 = f; break
|
||||||
if to_i8p is None:
|
if from_i64 is None:
|
||||||
to_i8p = ir.Function(builder.module, ir.FunctionType(i8p, [i64]), name='nyash.string.to_i8p_h')
|
from_i64 = ir.Function(builder.module, ir.FunctionType(i64, [i64]), name='nyash.box.from_i64')
|
||||||
# Ensure we pass an i64 handle
|
return builder.call(from_i64, [i64_val], name=f"{tag}_box_h_{dst}")
|
||||||
hv = val
|
|
||||||
if hv is None:
|
|
||||||
hv = ir.Constant(i64, 0)
|
|
||||||
if hasattr(hv, 'type') and isinstance(hv.type, ir.PointerType):
|
|
||||||
hv = builder.ptrtoint(hv, i64, name=f"bin_p2h_{tag}_{dst}")
|
|
||||||
elif hasattr(hv, 'type') and isinstance(hv.type, ir.IntType) and hv.type.width != 64:
|
|
||||||
hv = builder.zext(hv, i64, name=f"bin_zext_h_{tag}_{dst}")
|
|
||||||
return builder.call(to_i8p, [hv], name=f"bin_h2p_{tag}_{dst}")
|
|
||||||
|
|
||||||
# Resolve numeric side as i64 value
|
# Convert both operands to handles
|
||||||
def as_i64(val):
|
lhs_handle = to_handle(lhs, lhs_raw, lhs_val, 'lhs', lhs_tag)
|
||||||
if val is None:
|
rhs_handle = to_handle(rhs, rhs_raw, rhs_val, 'rhs', rhs_tag)
|
||||||
return ir.Constant(i64, 0)
|
|
||||||
if hasattr(val, 'type') and isinstance(val.type, ir.PointerType):
|
|
||||||
return builder.ptrtoint(val, i64, name=f"bin_p2i_{dst}")
|
|
||||||
if hasattr(val, 'type') and isinstance(val.type, ir.IntType) and val.type.width != 64:
|
|
||||||
return builder.zext(val, i64, name=f"bin_zext_i_{dst}")
|
|
||||||
return val
|
|
||||||
|
|
||||||
if lhs_tag:
|
# Use concat_hh which handles any type (string, integer, etc.)
|
||||||
lp = to_i8p_from_vid(lhs, lhs_raw, lhs_val, 'l')
|
concat_hh = None
|
||||||
ri = as_i64(rhs_val)
|
for f in builder.module.functions:
|
||||||
cf = None
|
if f.name == 'nyash.string.concat_hh':
|
||||||
for f in builder.module.functions:
|
concat_hh = f; break
|
||||||
if f.name == 'nyash.string.concat_si':
|
if concat_hh is None:
|
||||||
cf = f; break
|
hh_fnty = ir.FunctionType(i64, [i64, i64])
|
||||||
if cf is None:
|
concat_hh = ir.Function(builder.module, hh_fnty, name='nyash.string.concat_hh')
|
||||||
cf = ir.Function(builder.module, ir.FunctionType(i8p, [i8p, i64]), name='nyash.string.concat_si')
|
|
||||||
p = builder.call(cf, [lp, ri], name=f"concat_si_{dst}")
|
result = builder.call(concat_hh, [lhs_handle, rhs_handle], name=f"concat_hh_mixed_{dst}")
|
||||||
boxer = None
|
safe_vmap_write(vmap, dst, result, "binop_concat_hh_mixed")
|
||||||
for f in builder.module.functions:
|
|
||||||
if f.name == 'nyash.box.from_i8_string':
|
|
||||||
boxer = f; break
|
|
||||||
if boxer is None:
|
|
||||||
boxer = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
|
|
||||||
safe_vmap_write(vmap, dst, builder.call(boxer, [p], name=f"concat_box_{dst}"), "binop_concat_si")
|
|
||||||
else:
|
|
||||||
li = as_i64(lhs_val)
|
|
||||||
rp = to_i8p_from_vid(rhs, rhs_raw, rhs_val, 'r')
|
|
||||||
cf = None
|
|
||||||
for f in builder.module.functions:
|
|
||||||
if f.name == 'nyash.string.concat_is':
|
|
||||||
cf = f; break
|
|
||||||
if cf is None:
|
|
||||||
cf = ir.Function(builder.module, ir.FunctionType(i8p, [i64, i8p]), name='nyash.string.concat_is')
|
|
||||||
p = builder.call(cf, [li, rp], name=f"concat_is_{dst}")
|
|
||||||
boxer = None
|
|
||||||
for f in builder.module.functions:
|
|
||||||
if f.name == 'nyash.box.from_i8_string':
|
|
||||||
boxer = f; break
|
|
||||||
if boxer is None:
|
|
||||||
boxer = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
|
|
||||||
safe_vmap_write(vmap, dst, builder.call(boxer, [p], name=f"concat_box_{dst}"), "binop_concat_is")
|
|
||||||
# Tag result as string handle so subsequent '+' stays in string domain
|
# Tag result as string handle so subsequent '+' stays in string domain
|
||||||
try:
|
try:
|
||||||
if resolver is not None and hasattr(resolver, 'mark_string'):
|
if resolver is not None and hasattr(resolver, 'mark_string'):
|
||||||
|
|||||||
@ -55,10 +55,20 @@ def lower_global_call(builder, module, func_name, args, dst_vid, vmap, resolver,
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not func:
|
if not func:
|
||||||
# Create function declaration with i64 signature
|
# Create function declaration with appropriate signature
|
||||||
ret_type = ir.IntType(64)
|
# Phase 131-15-P1: Handle C ABI extern functions (print, panic, error)
|
||||||
arg_types = [ir.IntType(64)] * len(args)
|
i8p = ir.IntType(8).as_pointer()
|
||||||
func_type = ir.FunctionType(ret_type, arg_types)
|
if func_name in ["print", "panic", "error"]:
|
||||||
|
# C ABI: void(i8*)
|
||||||
|
func_type = ir.FunctionType(ir.VoidType(), [i8p])
|
||||||
|
elif func_name == "nyash.console.log":
|
||||||
|
# C ABI: i64(i8*)
|
||||||
|
func_type = ir.FunctionType(ir.IntType(64), [i8p])
|
||||||
|
else:
|
||||||
|
# Default: i64(...i64)
|
||||||
|
ret_type = ir.IntType(64)
|
||||||
|
arg_types = [ir.IntType(64)] * len(args)
|
||||||
|
func_type = ir.FunctionType(ret_type, arg_types)
|
||||||
func = ir.Function(module, func_type, name=func_name)
|
func = ir.Function(module, func_type, name=func_name)
|
||||||
|
|
||||||
# Prepare arguments with type conversion
|
# Prepare arguments with type conversion
|
||||||
@ -72,7 +82,21 @@ def lower_global_call(builder, module, func_name, args, dst_vid, vmap, resolver,
|
|||||||
if i < len(func.args):
|
if i < len(func.args):
|
||||||
expected_type = func.args[i].type
|
expected_type = func.args[i].type
|
||||||
if expected_type.is_pointer and isinstance(arg_val.type, ir.IntType):
|
if expected_type.is_pointer and isinstance(arg_val.type, ir.IntType):
|
||||||
arg_val = builder.inttoptr(arg_val, expected_type, name=f"global_i2p_{i}")
|
# Phase 131-15-P1: Convert i64 handle to i8* for C ABI functions
|
||||||
|
# Use nyash.string.to_i8p_h for proper handle-to-pointer conversion
|
||||||
|
if arg_val.type.width == 64:
|
||||||
|
to_i8p = None
|
||||||
|
for f in module.functions:
|
||||||
|
if f.name == "nyash.string.to_i8p_h":
|
||||||
|
to_i8p = f
|
||||||
|
break
|
||||||
|
if not to_i8p:
|
||||||
|
i8p = ir.IntType(8).as_pointer()
|
||||||
|
to_i8p_type = ir.FunctionType(i8p, [ir.IntType(64)])
|
||||||
|
to_i8p = ir.Function(module, to_i8p_type, name="nyash.string.to_i8p_h")
|
||||||
|
arg_val = builder.call(to_i8p, [arg_val], name=f"global_h2p_{i}")
|
||||||
|
else:
|
||||||
|
arg_val = builder.inttoptr(arg_val, expected_type, name=f"global_i2p_{i}")
|
||||||
elif isinstance(expected_type, ir.IntType) and arg_val.type.is_pointer:
|
elif isinstance(expected_type, ir.IntType) and arg_val.type.is_pointer:
|
||||||
arg_val = builder.ptrtoint(arg_val, expected_type, name=f"global_p2i_{i}")
|
arg_val = builder.ptrtoint(arg_val, expected_type, name=f"global_p2i_{i}")
|
||||||
|
|
||||||
|
|||||||
@ -303,8 +303,8 @@ pub fn emit_mir_json_for_harness(
|
|||||||
B::Or => "|",
|
B::Or => "|",
|
||||||
};
|
};
|
||||||
let mut obj = json!({"op":"binop","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()});
|
let mut obj = json!({"op":"binop","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()});
|
||||||
// Phase 131-11-E: dst_type hint based on RESULT type (not operand types)
|
// Phase 131-15-P1: dst_type only when type is KNOWN (not Unknown)
|
||||||
// Use the dst type from metadata, which has been corrected by repropagate_binop_types
|
// Operand TypeFacts take priority over dst_type hint in Python
|
||||||
if matches!(op, B::Add) {
|
if matches!(op, B::Add) {
|
||||||
let dst_type = f.metadata.value_types.get(dst);
|
let dst_type = f.metadata.value_types.get(dst);
|
||||||
match dst_type {
|
match dst_type {
|
||||||
@ -316,8 +316,12 @@ pub fn emit_mir_json_for_harness(
|
|||||||
// Explicitly mark as i64 for integer addition
|
// Explicitly mark as i64 for integer addition
|
||||||
obj["dst_type"] = json!("i64");
|
obj["dst_type"] = json!("i64");
|
||||||
}
|
}
|
||||||
|
Some(MirType::Unknown) | None => {
|
||||||
|
// Unknown: DO NOT emit dst_type
|
||||||
|
// Let Python side infer from operand TypeFacts
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Unknown/other: default to i64 (conservative)
|
// Other known types: use conservative i64
|
||||||
obj["dst_type"] = json!("i64");
|
obj["dst_type"] = json!("i64");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -688,8 +692,8 @@ pub fn emit_mir_json_for_harness_bin(
|
|||||||
B::Or => "|",
|
B::Or => "|",
|
||||||
};
|
};
|
||||||
let mut obj = json!({"op":"binop","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()});
|
let mut obj = json!({"op":"binop","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()});
|
||||||
// Phase 131-11-E: dst_type hint based on RESULT type (not operand types)
|
// Phase 131-15-P1: dst_type only when type is KNOWN (not Unknown)
|
||||||
// Use the dst type from metadata, which has been corrected by repropagate_binop_types
|
// Operand TypeFacts take priority over dst_type hint in Python
|
||||||
if matches!(op, B::Add) {
|
if matches!(op, B::Add) {
|
||||||
let dst_type = f.metadata.value_types.get(dst);
|
let dst_type = f.metadata.value_types.get(dst);
|
||||||
match dst_type {
|
match dst_type {
|
||||||
@ -701,8 +705,12 @@ pub fn emit_mir_json_for_harness_bin(
|
|||||||
// Explicitly mark as i64 for integer addition
|
// Explicitly mark as i64 for integer addition
|
||||||
obj["dst_type"] = json!("i64");
|
obj["dst_type"] = json!("i64");
|
||||||
}
|
}
|
||||||
|
Some(MirType::Unknown) | None => {
|
||||||
|
// Unknown: DO NOT emit dst_type
|
||||||
|
// Let Python side infer from operand TypeFacts
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Unknown/other: default to i64 (conservative)
|
// Other known types: use conservative i64
|
||||||
obj["dst_type"] = json!("i64");
|
obj["dst_type"] = json!("i64");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user