Skip to content

Feature/v0.14.1#12

Merged
shadowlink0122 merged 31 commits intomainfrom
feature/v0.14.1
Feb 20, 2026
Merged

Feature/v0.14.1#12
shadowlink0122 merged 31 commits intomainfrom
feature/v0.14.1

Conversation

@shadowlink0122
Copy link
Owner

@shadowlink0122 shadowlink0122 commented Feb 16, 2026

English

v0.14.1 Release - Cm言語コンパイラ

概要

v0.14.1はUEFIコンパイラバグ17件の修正typedef算術演算サポートGCC/Linux CIビルド修正を含むパッチリリースです。CosmOS UEFI開発で発見されたコンパイラバグを全件修正し、回帰テストを多数追加。整数型出力の完全対応、naked関数コード生成の根本修正、MIR最適化パスのASM対応など広範な安定化を実施しています。

v0.14.0の主な変更: JavaScriptバックエンドの大規模改善演算子オーバーロードの設計改善ベアメタル/UEFIサポートインラインユニオン型 (int | null)プラットフォームディレクティブVSCode拡張機能の品質改善


🔥 v0.14.1 変更点

UEFIコンパイラバグ全件修正(Bug#1〜#17)

CosmOS UEFI開発中に発見されたコンパイラバグ17件を全て修正しました。

Bug# 問題 修正内容
#1 char switchの符号拡張 switch_intのchar型比較を修正
#5 LICM最適化がASM出力変数を移動 ASM出力変数をループ不変と誤判定しない修正
#6 constant foldingのASM出力追跡 ASM→変数代入チェーンの最適化を抑制
#7 utiny*デリファレンスのi32切り詰め ポインタデリファレンスの型幅修正
#8 int→utinyキャストの符号拡張 trunc命令の正しい適用
#9 構造体フィールドの定数畳み込み フィールドアクセスの最適化を抑制
#10 ptr->method()のself書き戻し MIR loweringでポインタ経由の書き戻し実装
#11 UEFIレジスタマッピング不正 x86_64 UEFI ABIに合わせたレジスタリマップ
#12 naked関数のプロローグ/エピローグ干渉 Naked+$N事前置換方式に統一
#13 LLVM最適化によるcall/ret消滅 UEFIターゲットの最適化レベル調整
#14 構造体配列の全体再代入でゴミ値 memcpy/配列代入の型サイズ修正
#15 非export関数がexport関数から呼出不可 シンボル解決の修正
#16 &local as ulongキャスト不正 ポインタ→整数キャストの修正
#17 UEFIスタックプローブクラッシュ スタックプローブの無効化

typedef算術演算サポート

typedef型の値に対する算術演算と、typedef引数のstatic→static関数呼び出し時の型不整合を修正しました。

typedef EFI_STATUS = ulong;
EFI_STATUS status = 0;
if (status != 0) { /* 修正前: コンパイルエラー → 修正後: 正常動作 */ }

整数型出力の完全対応

MIR loweringのprintln関数選択ロジックにlong/ulong/uint/isize/usize型のケースを追加。

GCC/Linux CIビルド修正

src/mir/nodes.hpp<unordered_map>ヘッダーを追加。AppleClangでは間接インクルードで解決されていたが、GCCでは明示的なインクルードが必要。

JS/WASMランタイム改善

ファイル 変更内容
src/codegen/js/builtins.cpp cm_println_long/ulong/uintとformat/to_string版追加
src/codegen/llvm/wasm/runtime_print.c cm_println_long/ulong出力関数追加
src/codegen/llvm/core/operators.cpp ビット演算(BitAnd/BitOr/BitXor)の型幅統一ロジック追加
src/mir/lowering/expr_call.cpp println型選択にlong/ulong/uint/isize/usizeケース追加
src/mir/passes/interprocedural/inlining.cpp ASM含有関数のインライン展開禁止

🎯 主要な変更

1. JSバックエンド大規模リファクタリング

JSコードジェネレータを大幅にリファクタリングし、1,600行以上の不要コードを削除しました。

変更 詳細
codegen.cpp -1,618行(大規模整理)
emit_expressions.cpp +124行(式出力改善)
emit_statements.cpp +80行(文出力改善)
builtins.hpp +71行(ビルトイン拡充)

JSテスト通過率

バージョン パス 失敗 スキップ 通過率
v0.13.1 206 119 47 55%
v0.14.0 298 0 49 87%

JSコンパイルの使い方

./cm compile --target=js hello.cm -o output.js
node output.js

2. 演算子オーバーロード改善

impl T { operator ... } 構文

演算子をimpl T for InterfaceNameではなく、直接impl T { operator ... }で定義可能になりました。

struct Vec2 { int x; int y; }

impl Vec2 {
    operator Vec2 +(Vec2 other) {
        return Vec2{x: self.x + other.x, y: self.y + other.y};
    }
}

複合代入演算子

二項演算子を定義すると、対応する複合代入演算子が自動的に使えます。

Vec2 v = Vec2{x: 10, y: 20};
v += Vec2{x: 5, y: 3};   // v = v + Vec2{5, 3} と同等
v -= Vec2{x: 2, y: 1};   // v = v - Vec2{2, 1} と同等

サポートする複合代入演算子: +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=

ビット演算子オーバーロード

&, |, ^, <<, >> の全ビット演算子をオーバーロード可能になりました。

interface存在チェック

impl T for II が宣言済みinterfaceでない場合、コンパイルエラーになります。

3. インラインユニオン型とnull型

インラインユニオン構文 (int | null)

typedefなしで直接ユニオン型を使用可能になりました。

int | null a = null;           // nullが代入可能
int | null b = 42 as MaybeInt; // int値も代入可能
int | string | null c = null;  // 3型以上のユニオンも可能
変更 詳細
null型追加 TypeKind::Nullmake_null()
parse_type_with_union() 変数宣言・関数戻り値・構造体フィールドで使用
型互換性 Unionメンバー型とのnull代入・値代入に対応

4. プラットフォームディレクティブ

ファイル先頭に //! platform: で実行可能なプラットフォームを制約可能。

//! platform: native
// このファイルはLLVM Native/JITでのみコンパイル可能

対応プラットフォーム: native, js, wasm, uefi, baremetal

5. ベアメタル / UEFIサポート

--target=uefi でUEFIアプリケーションをコンパイル可能。QEMUでHello World出力確認済み。

// UEFI Hello World
import ./libs/efi_core;
import ./libs/efi_text;

ulong efi_main(void* image_handle, void* system_table) {
    efi_clear_screen(system_table);
    string msg = "Hello World from Cm!";
    efi_println(system_table, msg as void*);
    while (true) { __asm__("hlt"); }
    return 0;
}
  • インラインASM自動クロバー検出を実装
  • UEFIライブラリ (libs/uefi/) を新規作成
  • ベアメタル向けno_std実行プロファイル対応

6. VSCode拡張機能の追加と改善

拡張機能の新規追加

Cm言語用VSCode拡張機能を新規作成しました。

  • 構文ハイライト: TextMate文法定義 (cm.tmLanguage.json)
  • ファイルアイコン: .cmファイルにCmアイコンを表示
  • 言語設定: ブラケットマッチング、折りたたみ、インデント支援
  • VSIXパッケージ: pnpm run package でインストール可能なパッケージを生成

TypeScript移行 + ESLint/Prettier導入

スクリプト(update-version, verify-version)をJavaScript→TypeScriptに移行し、ESLint + Prettierで品質管理を自動化。

ツール 設定 コマンド
TypeScript tsconfig.json (strict, ES2020) pnpm run compile
ESLint eslint.config.mjs (Flat Config v9+) pnpm run lint
Prettier .prettierrc pnpm run format:check

CI統合

ci.ymlextension-lint ジョブを追加。push/PRごとにcompile → lint → format:checkを自動チェック。

7. サンプルプロジェクト

Webアプリサンプル (examples/web-app/)

Cm言語でロジックを記述し、JSバックエンドでコンパイルしてブラウザ上で動作するWebアプリのサンプルを追加。HTMLテンプレートをバッククォート複数行文字列で記述する構成。

UEFIサンプル (examples/uefi/)

UEFI Hello Worldプログラムをexamples/uefi/に整理。QEMUでの実行手順を含む。


🐛 バグ修正

v0.14.1 修正(UEFIコンパイラバグ + 言語機能)

問題 原因 修正ファイル
Bug#1 char switchの符号拡張 switch_intがchar型を符号拡張 mir_to_llvm.cpp
Bug#5 LICMがASM出力を移動 ASM出力変数をループ不変と誤判定 licm.cpp
Bug#6 constant foldingのASM追跡 ASM→変数代入の最適化抑制漏れ folding.cpp
Bug#7 utiny*デリファレンスi32切り詰め ポインタデリファレンスの型幅不正 mir_to_llvm.cpp
Bug#8 int→utinyキャスト不正 trunc命令の適用ミス mir_to_llvm.cpp
Bug#9 構造体フィールドの定数畳み込み フィールドアクセス最適化の抑制漏れ folding.cpp
Bug#10 ptr->method()のself書き戻し ポインタ経由の書き戻し未実装 stmt.cpp (MIR)
Bug#11 UEFIレジスタマッピング不正 UEFI ABIレジスタリマップ欠落 mir_to_llvm.cpp
Bug#12 naked関数のプロローグ干渉 module-asm方式の不具合 mir_to_llvm.cpp
Bug#13 LLVM最適化でcall/ret消滅 最適化レベル調整 codegen.cpp (native)
Bug#14 構造体配列の再代入でゴミ値 配列代入のサイズ計算不正 mir_to_llvm.cpp
Bug#15 非export関数がexportから呼出不可 シンボル解決の不備 import.cpp
Bug#16 &local as ulongキャスト不正 ポインタ→整数キャスト未対応 mir_to_llvm.cpp
Bug#17 UEFIスタックプローブクラッシュ スタックプローブの無効化 codegen.cpp (native)
typedef算術演算エラー typedef型のis_numeric判定漏れ checking/expr.cpp
typedef引数の型不整合 static→static呼び出し時の型解決 monomorphization_impl.cpp
GCC CIビルドエラー <unordered_map>ヘッダー未インクルード nodes.hpp
大きな16進リテラルのprintln println関数選択にlong/ulong未対応 expr_call.cpp
ビット演算の型幅不一致 BitAnd/BitOr/BitXorに型統一ロジック欠落 operators.cpp
ASM関数のインライン展開 ASM含有関数がインライン展開 inlining.cpp
JS/WASMでlong/ulong未出力 ランタイムにcm_println_long等未定義 builtins.cpp, runtime_print.c

v0.14.0 修正

問題 原因 修正ファイル
println型判定の誤り AST型チェッカーがmatch armのpayload変数の型をintに設定 expr_call.cpp
ペイロードロードエラー Tagged Unionの非構造体ペイロード型がi32にハードコード mir_to_llvm.cpp
構造体ペイロードサイズ計算 max_payload_size()がStruct型をデフォルト8バイトで計算 types.cpp
プラットフォーム不一致セグフォ プラットフォーム制約のないファイルでクラッシュ プリプロセッサ修正
Boolean定数オペランドの型判定 JSバックエンドでBoolean定数の型が不正 JS codegen修正
並列テストのレースコンディション テストランナーのファイル名衝突 テストランナー修正

🔧 ビルド・テスト改善

JSスキップファイル整理

カテゴリ 理由
asm/ インラインアセンブリはJS非対応
io/ ファイルI/OはJS非対応
net/ TCP/HTTPはJS非対応
sync/ Mutex/Channel/AtomicはJS非対応
thread/ スレッドはJS非対応

CI改善

  • JSバックエンドテストをCIに追加
  • VERSIONファイルとブランチ名の整合チェックCI追加
  • VSCode拡張機能lint CIジョブ追加
  • UEFIコンパイルテストCIジョブ追加
  • ベアメタルコンパイルテストCIジョブ追加
  • GPUテスト全バックエンドスキップ
  • タイムアウトテスト根本修正
  • 不安定なoperator_comprehensiveテストを5つの個別テストに分割

テスト構成再編成

  • tests/test_programstests/programs にリネーム
  • ライブラリを libs/ 配下にプラットフォーム別で再構成

📁 変更ファイル一覧

JSバックエンド

ファイル 変更内容
src/codegen/js/codegen.cpp 大規模リファクタリング(-1,618行)
src/codegen/js/emit_expressions.cpp 式出力改善
src/codegen/js/emit_statements.cpp 文出力改善
src/codegen/js/builtins.hpp ビルトイン関数拡充
src/codegen/js/runtime.hpp ランタイムヘルパー追加
src/codegen/js/types.hpp 型マッピング改善

LLVMバックエンド/MIR修正

ファイル 変更内容
src/codegen/llvm/core/types.cpp Tagged Unionペイロードサイズ計算修正
src/codegen/llvm/core/mir_to_llvm.cpp ペイロードロード修正、自動クロバー検出、naked関数統一、Bug#1/7/8/11/14/16修正
src/codegen/llvm/native/codegen.cpp Bug#13/17: UEFI最適化レベル調整、スタックプローブ無効化
src/mir/lowering/expr_call.cpp println型判定修正
src/mir/lowering/stmt.cpp Bug#10: ptr->method()のself書き戻し
src/mir/lowering/monomorphization_impl.cpp typedef引数の型解決修正
src/mir/lowering/impl.cpp impl lowering改善
src/mir/passes/scalar/folding.cpp Bug#6/9: ASM出力・構造体フィールド最適化抑制
src/mir/passes/loop/licm.cpp Bug#5: ASM出力変数のループ不変判定修正
src/mir/nodes.hpp GCC CIビルド修正(unordered_mapヘッダー追加)
src/frontend/types/checking/expr.cpp typedef算術演算サポート
src/preprocessor/import.cpp Bug#15: 非export関数のシンボル解決修正

型チェッカー/パーサー

ファイル 変更内容
src/frontend/parser/parser.hpp impl T { operator ... } 構文、インラインユニオン型
src/frontend/types/checking/expr.cpp 複合代入演算子の構造体オーバーロード対応
src/frontend/types/checking/decl.cpp interface存在チェック・operator自動登録

VSCode拡張機能

ファイル 変更内容
vscode-extension/ 拡張機能全体を新規追加
vscode-extension/syntaxes/cm.tmLanguage.json TextMate文法定義(710行)
vscode-extension/scripts/*.ts TypeScript版スクリプト
vscode-extension/eslint.config.mjs ESLint Flat Config
vscode-extension/.prettierrc Prettier設定
vscode-extension/tsconfig.json TypeScript設定
.github/workflows/ci.yml extension-lintジョブ、baremetal-testジョブ追加

チュートリアル・ドキュメント

ファイル 変更内容
docs/tutorials/ja/advanced/operators.md 演算子チュートリアル全面改訂
docs/tutorials/en/advanced/operators.md 英語版演算子チュートリアル全面改訂
docs/tutorials/ja/basics/operators.md ビット演算子チュートリアル追加
docs/tutorials/ja/basics/setup.md エディタ設定セクション大幅拡充
docs/tutorials/ja/compiler/uefi.md UEFIチュートリアル追加
docs/tutorials/ja/compiler/js-compilation.md JSチュートリアル追加
docs/releases/v0.14.0.md リリースノート更新
docs/QUICKSTART.md VSCode拡張機能リンク追加
vscode-extension/README.md 開発ガイド全面刷新

テスト

ファイル 変更内容
tests/programs/interface/operator_arithmetic.* 算術演算子テスト(分割)
tests/programs/interface/operator_compare.* 比較演算子テスト(分割)
tests/programs/interface/operator_bitwise.* ビット演算子テスト(分割)
tests/programs/interface/operator_compound_assign.* 算術複合代入テスト(分割)
tests/programs/interface/operator_bitwise_assign.* ビット複合代入テスト(分割)
tests/programs/interface/operator_add.* impl T構文テスト
tests/programs/enum/associated_data.* .error → .expected
tests/programs/asm/.skip JSスキップファイル追加
tests/unified_test_runner.sh テストランナー改善
tests/programs/uefi/uefi_compile/* UEFIコンパイルテスト多数追加(Bug#1-17回帰テスト含む)
tests/programs/baremetal/allowed/* ベアメタルテスト3件追加(enum/配列/ポインタ)
tests/programs/common/types/ptr_to_int_cast.* ポインタ→整数キャストテスト追加
tests/programs/common/types/typedef_compound_assign.* typedef算術演算テスト追加

サンプル

ファイル 変更内容
examples/web-app/ Webアプリサンプル追加
examples/uefi/ UEFIサンプル整理

🧪 テスト状況

バックエンド 通過 失敗 スキップ
JIT (O0) 347 0 4
LLVM Native 380 0 7
LLVM WASM 346 0 5
JavaScript 306 0 49
Baremetal 11 0 0
UEFI 5 0 0

📊 統計

  • テスト総数: 351
  • JIT通過: 347(0失敗)
  • LLVM通過: 380(0失敗)
  • WASM通過: 346(0失敗)
  • JS通過: 306(0失敗、v0.13.1の206から+100改善)

✅ チェックリスト

  • make tip 全テスト通過(347 PASS / 0 FAIL)
  • make tlp 全テスト通過(380 PASS / 0 FAIL)
  • make tw 全テスト通過(346 PASS / 0 FAIL)
  • make tjp 全テスト通過(306 PASS / 0 FAIL)
  • VSCode拡張機能 lint通過(compile + ESLint + Prettier)
  • ベアメタルテスト通過(11 PASS / 0 FAIL)
  • UEFIテスト通過(5 PASS / 0 FAIL)
  • GCC/Linux CIビルド通過
  • リリースノート更新(docs/releases/v0.14.0.md
  • チュートリアル更新(演算子、UEFI、JS、環境構築)
  • VSCode拡張機能README更新
  • QUICKSTART.md更新
  • ローカルパス情報なし

リリース日: 2026年2月19日
バージョン: v0.14.1

- MIR lowering: printlnのlong/ulong/uint型対応追加 (expr_call.cpp)
- LLVM codegen: ビット演算の型幅統一ロジック追加 (operators.cpp)
- JSランタイム: cm_println_long/ulong/uint とformat/to_string版追加 (builtins.cpp)
- WASMランタイム: cm_println_long/ulong出力関数追加 (runtime_print.c)
- 回帰テスト4件追加: hex_literal_large, long_println, bitwise_type_widening, const_arithmetic
- 全バックエンド0 FAIL: tip(347), tlp(380), tjp(306), tlw(346)
Copilot AI review requested due to automatic review settings February 16, 2026 15:49
- MIR lowering: printlnのlong/ulong/uint型対応追加 (expr_call.cpp)
- LLVM codegen: ビット演算の型幅統一ロジック追加 (operators.cpp)
- JSランタイム: cm_println_long/ulong/uint とformat/to_string版追加 (builtins.cpp)
- WASMランタイム: cm_println_long/ulong出力関数追加 (runtime_print.c)
- 回帰テスト4件追加: hex_literal_large, long_println, bitwise_type_widening, const_arithmetic
- 全バックエンド0 FAIL: tip(347), tlp(380), tjp(306), tlw(346)
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces v0.14.1, a patch release that completes integer type output support across all backends (LLVM, WASM, JavaScript) and fixes several critical bugs related to 64-bit integer handling, bitwise operations, and inline assembly optimization.

Changes:

  • Fixed println output for long/ulong/uint types by adding runtime function dispatch in MIR lowering and implementing missing runtime functions in WASM/JS backends
  • Fixed lexer to handle hex literals >= 0x8000000000000000 using stoull instead of stoll to avoid overflow
  • Added bitwise operation type widening in LLVM codegen to fix type mismatch errors with mixed-width operands
  • Fixed ASM statement handling in optimization passes to prevent incorrect optimizations and inlining

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/programs/common/types/long_println.* New regression test for 64-bit integer println output
tests/programs/common/types/hex_literal_large.* New regression test for large hex literals (>= 0x80000000)
tests/programs/common/types/bitwise_type_widening.* New regression test for bitwise operations with mixed integer widths
tests/programs/common/const/const_arithmetic.* New regression test for const expression arithmetic
src/frontend/lexer/lexer.cpp Fixed integer literal parsing to use stoull with bit-cast for full uint64_t range
src/mir/lowering/expr_call.cpp Added type-specific dispatch for println (long/ulong/uint/isize/usize)
src/mir/lowering/expr_ops.cpp Added unsigned/signed distinction for long type promotion in binary operations
src/mir/lowering/expr_basic.cpp Enhanced literal type inference for large values and explicit type contexts
src/mir/passes/scalar/propagation.cpp Added ASM output constraint tracking to prevent incorrect copy propagation
src/mir/passes/scalar/folding.cpp Added ASM output constraint tracking to prevent incorrect constant folding
src/mir/passes/interprocedural/inlining.cpp Added check to prevent inlining of functions containing ASM statements
src/codegen/llvm/wasm/runtime_print.c Implemented cm_println_long/ulong functions for WASM runtime
src/codegen/llvm/emit/print.cpp Updated print handling (NOTE: file not compiled, dead code)
src/codegen/llvm/core/print_codegen.cpp Added 64-bit integer format/print support
src/codegen/llvm/core/operators.cpp Added type widening for bitwise operations (AND/OR/XOR)
src/codegen/llvm/core/mir_to_llvm.cpp Fixed integer cast to use zext for unsigned target types
src/codegen/llvm/core/types.cpp Removed debug comment
src/codegen/js/builtins.cpp Added long/ulong/uint variants for println/print/format functions
CMakeLists.txt Improved LLVM shared library finding with explicit path
docs/design/v0.14.1/bugs1.md Documentation of UEFI development bugs and workarounds
docs/design/v0.14.1/bugs2.md Documentation of additional bugs found in Cosmos OS development
VERSION Updated to 0.14.1
Comments suppressed due to low confidence (1)

src/codegen/llvm/emit/print.cpp:537

  • The entire src/codegen/llvm/emit/ directory appears to contain duplicate implementations of files from src/codegen/llvm/core/ (print.cpp duplicates print_codegen.cpp, operators.cpp, etc.), but these files are not included in CMakeLists.txt and are therefore dead code. If these files are intended as backup/alternative implementations, they should be documented. If they're accidental duplicates, they should be removed to avoid confusion. The changes in this PR that modify emit/print.cpp will have no effect since the file is not compiled.
/// @file print_codegen.cpp
/// @brief Print/Format関連のコード生成
/// terminator.cppから分離したprint/println/format処理

#include "../../../common/debug.hpp"
#include "mir_to_llvm.hpp"

#include <iostream>

namespace cm::codegen::llvm_backend {

// ============================================================
// Helper: 値を文字列に変換
// ============================================================

llvm::Value* MIRToLLVM::generateValueToString(llvm::Value* value, const hir::TypePtr& hirType) {
    auto valueType = value->getType();

    if (valueType->isPointerTy()) {
        // 既に文字列
        return value;
    }

    if (valueType->isIntegerTy()) {
        auto intType = llvm::cast<llvm::IntegerType>(valueType);
        bool isBoolType = hirType && hirType->kind == hir::TypeKind::Bool;
        bool isCharType = hirType && hirType->kind == hir::TypeKind::Char;
        bool isUnsigned =
            hirType &&
            (hirType->kind == hir::TypeKind::UTiny || hirType->kind == hir::TypeKind::UShort ||
             hirType->kind == hir::TypeKind::UInt || hirType->kind == hir::TypeKind::ULong);

        if (isBoolType) {
            auto boolVal = value;
            if (intType->getBitWidth() != 8) {
                boolVal = builder->CreateTrunc(value, ctx.getI8Type());
            }
            auto formatFunc = module->getOrInsertFunction(
                "cm_format_bool",
                llvm::FunctionType::get(ctx.getPtrType(), {ctx.getI8Type()}, false));
            return builder->CreateCall(formatFunc, {boolVal});
        }

        if (isCharType) {
            auto charVal = value;
            if (intType->getBitWidth() != 8) {
                charVal = builder->CreateTrunc(value, ctx.getI8Type());
            }
            auto formatFunc = module->getOrInsertFunction(
                "cm_format_char",
                llvm::FunctionType::get(ctx.getPtrType(), {ctx.getI8Type()}, false));
            return builder->CreateCall(formatFunc, {charVal});
        }

        // 整数型: 64bit整数は専用のフォーマット関数を使用
        unsigned srcBits = intType->getBitWidth();
        if (srcBits > 32) {
            // 64bit整数: cm_format_long / cm_format_ulong を使用
            auto longVal = value;
            if (srcBits != 64) {
                if (srcBits < 64) {
                    longVal = isUnsigned ? builder->CreateZExt(value, ctx.getI64Type())
                                         : builder->CreateSExt(value, ctx.getI64Type());
                } else {
                    longVal = builder->CreateTrunc(value, ctx.getI64Type());
                }
            }
            auto formatFunc = module->getOrInsertFunction(
                isUnsigned ? "cm_format_ulong" : "cm_format_long",
                llvm::FunctionType::get(ctx.getPtrType(), {ctx.getI64Type()}, false));
            return builder->CreateCall(formatFunc, {longVal});
        } else {
            // 32bit以下: cm_format_int / cm_format_uint を使用
            auto intVal = value;
            if (srcBits != 32) {
                if (srcBits < 32) {
                    intVal = isUnsigned ? builder->CreateZExt(value, ctx.getI32Type())
                                        : builder->CreateSExt(value, ctx.getI32Type());
                }
            }
            auto formatFunc = module->getOrInsertFunction(
                isUnsigned ? "cm_format_uint" : "cm_format_int",
                llvm::FunctionType::get(ctx.getPtrType(), {ctx.getI32Type()}, false));
            return builder->CreateCall(formatFunc, {intVal});
        }
    }

    if (valueType->isFloatingPointTy()) {
        auto doubleVal = value;
        if (valueType->isFloatTy()) {
            doubleVal = builder->CreateFPExt(value, ctx.getF64Type());
        }
        auto formatFunc = module->getOrInsertFunction(
            "cm_format_double",
            llvm::FunctionType::get(ctx.getPtrType(), {ctx.getF64Type()}, false));
        return builder->CreateCall(formatFunc, {doubleVal});
    }

    // 未対応の型
    return builder->CreateGlobalStringPtr("<?>");
}

// ============================================================
// Helper: フォーマット置換を生成
// ============================================================

llvm::Value* MIRToLLVM::generateFormatReplace(llvm::Value* currentStr, llvm::Value* value,
                                              const hir::TypePtr& hirType) {
    auto valueType = value->getType();

    // HIR型がPointer型の場合、ポインタを16進数で表示
    if (hirType && hirType->kind == hir::TypeKind::Pointer) {
        // ポインタを整数(64ビット)にキャストして16進数表示
        llvm::Value* ptrAsInt = nullptr;
        if (value->getType()->isPointerTy()) {
            ptrAsInt = builder->CreatePtrToInt(value, ctx.getI64Type(), "ptr_to_int");
        } else if (value->getType()->isIntegerTy()) {
            // すでに整数の場合(アドレス演算子の結果など)
            ptrAsInt = value;
        } else {
            return currentStr;
        }

        // cm_format_replace_ptr を使用してポインタをフォーマット
        // デフォルトで16進数表示、{:X} で大文字16進数
        auto replaceFunc = module->getOrInsertFunction(
            "cm_format_replace_ptr",
            llvm::FunctionType::get(ctx.getPtrType(), {ctx.getPtrType(), ctx.getI64Type()}, false));
        auto result = builder->CreateCall(replaceFunc, {currentStr, ptrAsInt});
        return result;
    }

    if (valueType->isPointerTy()) {
        // 文字列型(HIRがString型の場合)
        auto replaceFunc = module->getOrInsertFunction(
            "cm_format_replace_string",
            llvm::FunctionType::get(ctx.getPtrType(), {ctx.getPtrType(), ctx.getPtrType()}, false));
        return builder->CreateCall(replaceFunc, {currentStr, value});
    }

    if (valueType->isIntegerTy()) {
        auto intType = llvm::cast<llvm::IntegerType>(valueType);
        bool isBoolType = hirType && hirType->kind == hir::TypeKind::Bool;
        bool isCharType = hirType && hirType->kind == hir::TypeKind::Char;
        bool isUnsigned =
            hirType &&
            (hirType->kind == hir::TypeKind::UTiny || hirType->kind == hir::TypeKind::UShort ||
             hirType->kind == hir::TypeKind::UInt || hirType->kind == hir::TypeKind::ULong);

        if (isBoolType) {
            auto boolVal = value;
            if (intType->getBitWidth() != 8) {
                boolVal = builder->CreateTrunc(value, ctx.getI8Type());
            }
            auto formatFunc = module->getOrInsertFunction(
                "cm_format_bool",
                llvm::FunctionType::get(ctx.getPtrType(), {ctx.getI8Type()}, false));
            auto boolStr = builder->CreateCall(formatFunc, {boolVal});
            auto replaceFunc = module->getOrInsertFunction(
                "cm_format_replace",
                llvm::FunctionType::get(ctx.getPtrType(), {ctx.getPtrType(), ctx.getPtrType()},
                                        false));
            return builder->CreateCall(replaceFunc, {currentStr, boolStr});
        }

        if (isCharType) {
            auto charVal = value;
            if (intType->getBitWidth() != 8) {
                charVal = builder->CreateTrunc(value, ctx.getI8Type());
            }
            auto formatFunc = module->getOrInsertFunction(
                "cm_format_char",
                llvm::FunctionType::get(ctx.getPtrType(), {ctx.getI8Type()}, false));
            auto charStr = builder->CreateCall(formatFunc, {charVal});
            auto replaceFunc = module->getOrInsertFunction(
                "cm_format_replace",
                llvm::FunctionType::get(ctx.getPtrType(), {ctx.getPtrType(), ctx.getPtrType()},
                                        false));
            return builder->CreateCall(replaceFunc, {currentStr, charStr});
        }

        // 整数型
        unsigned srcBits = intType->getBitWidth();

        // 64ビット整数の場合は専用の関数を使用
        if (srcBits > 32) {
            // アドレスやlongの値は64ビットとして処理
            auto longVal = value;
            if (srcBits != 64) {
                if (srcBits < 64) {
                    longVal = isUnsigned ? builder->CreateZExt(value, ctx.getI64Type())
                                         : builder->CreateSExt(value, ctx.getI64Type());
                } else {
                    longVal = builder->CreateTrunc(value, ctx.getI64Type());
                }
            }

            auto replaceFunc = module->getOrInsertFunction(
                isUnsigned ? "cm_format_replace_ulong" : "cm_format_replace_long",
                llvm::FunctionType::get(ctx.getPtrType(), {ctx.getPtrType(), ctx.getI64Type()},
                                        false));
            return builder->CreateCall(replaceFunc, {currentStr, longVal});
        } else {
            // 32ビット以下の場合は従来通り
            auto intVal = value;
            if (srcBits != 32) {
                if (srcBits < 32) {
                    intVal = isUnsigned ? builder->CreateZExt(value, ctx.getI32Type())
                                        : builder->CreateSExt(value, ctx.getI32Type());
                } else {
                    intVal = builder->CreateTrunc(value, ctx.getI32Type());
                }
            }

            auto replaceFunc = module->getOrInsertFunction(
                isUnsigned ? "cm_format_replace_uint" : "cm_format_replace_int",
                llvm::FunctionType::get(ctx.getPtrType(), {ctx.getPtrType(), ctx.getI32Type()},
                                        false));
            return builder->CreateCall(replaceFunc, {currentStr, intVal});
        }
    }

    if (valueType->isFloatingPointTy()) {
        auto doubleVal = value;
        if (valueType->isFloatTy()) {
            doubleVal = builder->CreateFPExt(value, ctx.getF64Type());
        }
        auto replaceFunc = module->getOrInsertFunction(
            "cm_format_replace_double",
            llvm::FunctionType::get(ctx.getPtrType(), {ctx.getPtrType(), ctx.getF64Type()}, false));
        return builder->CreateCall(replaceFunc, {currentStr, doubleVal});
    }

    // 未対応の型はそのまま返す
    return currentStr;
}

// ============================================================
// cm_println_format / cm_print_format の処理
// ============================================================

void MIRToLLVM::generatePrintFormatCall(const mir::MirTerminator::CallData& callData,
                                        bool isNewline) {
    if (callData.args.size() < 2)
        return;

    // MIR形式: [format_string, arg_count, arg1, arg2, ...]
    auto formatStr = convertOperand(*callData.args[0]);
    llvm::Value* currentStr = formatStr;

    // {{と}}のエスケープ処理
    auto unescapeFunc = module->getOrInsertFunction(
        "cm_format_unescape_braces",
        llvm::FunctionType::get(ctx.getPtrType(), {ctx.getPtrType()}, false));
    // LLVM 15以降ではopaque pointerなのでキャスト不要

    currentStr = builder->CreateCall(unescapeFunc, {formatStr});

    // 引数インデックス2以降が実際の値
    for (size_t i = 2; i < callData.args.size(); ++i) {
        auto value = convertOperand(*callData.args[i]);
        auto hirType = getOperandType(*callData.args[i]);
        currentStr = generateFormatReplace(currentStr, value, hirType);
    }

    // 結果を出力
    auto printFunc = module->getOrInsertFunction(
        isNewline ? "cm_println_string" : "cm_print_string",
        llvm::FunctionType::get(ctx.getVoidType(), {ctx.getPtrType()}, false));
    builder->CreateCall(printFunc, {currentStr});
}

// ============================================================
// cm_format_string の処理
// ============================================================

void MIRToLLVM::generateFormatStringCall(const mir::MirTerminator::CallData& callData) {
    if (callData.args.size() < 2)
        return;

    // MIR形式: [format_string, arg_count, arg1, arg2, ...]
    auto formatStr = convertOperand(*callData.args[0]);
    llvm::Value* currentStr = formatStr;

    // {{と}}のエスケープ処理
    auto unescapeFunc = module->getOrInsertFunction(
        "cm_format_unescape_braces",
        llvm::FunctionType::get(ctx.getPtrType(), {ctx.getPtrType()}, false));
    // LLVM 15以降ではopaque pointerなのでキャスト不要

    currentStr = builder->CreateCall(unescapeFunc, {formatStr});

    // 引数インデックス2以降が実際の値
    for (size_t i = 2; i < callData.args.size(); ++i) {
        auto value = convertOperand(*callData.args[i]);
        auto hirType = getOperandType(*callData.args[i]);
        currentStr = generateFormatReplace(currentStr, value, hirType);
    }

    // 結果をローカル変数に格納
    if (callData.destination.has_value()) {
        auto destPlace = callData.destination.value();
        auto destLocal = locals[destPlace.local];
        if (destLocal) {
            builder->CreateStore(currentStr, destLocal);
        }
    }
}

// ============================================================
// print/println の処理
// ============================================================

void MIRToLLVM::generatePrintCall(const mir::MirTerminator::CallData& callData, bool isNewline) {
    // std::cout << "[CODEGEN] generatePrintCall Start\n" << std::flush;
    if (callData.args.empty()) {
        // 引数なしの場合:改行のみ出力
        if (isNewline) {
            auto printFunc = module->getOrInsertFunction(
                "cm_println_string",
                llvm::FunctionType::get(ctx.getVoidType(), {ctx.getPtrType()}, false));
            auto emptyStr = builder->CreateGlobalStringPtr("", "empty_str");
            builder->CreateCall(printFunc, {emptyStr});
        }
        // std::cout << "[CODEGEN] generatePrintCall End (Empty)\n" << std::flush;
        return;
    }

    if (callData.args.size() >= 2) {
        // 複数引数の場合
        // std::cout << "[CODEGEN] generatePrintCall Multiple Args: " << callData.args.size() <<
        // "\n"
        //           << std::flush;
        auto firstArg = convertOperand(*callData.args[0]);
        auto firstType = firstArg->getType();

        if (firstType->isPointerTy()) {
            // std::cout << "[CODEGEN] generatePrintCall Format String Processing\n" << std::flush;
            // 最初の引数が文字列の場合:フォーマット文字列として処理
            llvm::Value* formattedStr = nullptr;

            // フォーマット指定子({:})があるかチェック
            bool hasFormatSpecifiers = false;
            if (auto globalStr =
                    llvm::dyn_cast<llvm::GlobalVariable>(firstArg->stripPointerCasts())) {
                if (globalStr->hasInitializer()) {
                    if (auto constData =
                            llvm::dyn_cast<llvm::ConstantDataArray>(globalStr->getInitializer())) {
                        std::string str = constData->getAsString().str();
                        hasFormatSpecifiers = str.find("{:") != std::string::npos;
                    }
                }
            }

            // WASM用の特別処理(フォーマット指定子がない場合のみ)
            if (ctx.getTargetConfig().target == BuildTarget::Wasm && callData.args.size() >= 3 &&
                callData.args.size() <= 6 && !hasFormatSpecifiers) {
                std::vector<llvm::Value*> stringArgs;
                stringArgs.push_back(firstArg);

                for (size_t i = 2; i < callData.args.size(); ++i) {
                    auto value = convertOperand(*callData.args[i]);
                    auto hirType = getOperandType(*callData.args[i]);
                    auto strValue = generateValueToString(value, hirType);
                    if (strValue) {
                        stringArgs.push_back(strValue);
                    }
                }

                // 引数の数に応じて適切な関数を呼び出す
                const char* funcNames[] = {nullptr, "cm_format_string_1", "cm_format_string_2",
                                           "cm_format_string_3", "cm_format_string_4"};
                size_t numArgs = stringArgs.size() - 1;  // フォーマット文字列を除く

                if (numArgs >= 1 && numArgs <= 4) {
                    std::vector<llvm::Type*> paramTypes(stringArgs.size(), ctx.getPtrType());
                    auto formatFunc = module->getOrInsertFunction(
                        funcNames[numArgs],
                        llvm::FunctionType::get(ctx.getPtrType(), paramTypes, false));
                    formattedStr = builder->CreateCall(formatFunc, stringArgs);
                }
            }

            // WASMで処理されなかった場合は従来の処理
            if (!formattedStr) {
                llvm::Value* currentStr = firstArg;

                // MIR形式: [format_string, arg_count, arg1, arg2, ...]
                size_t startIdx = 2;
                if (callData.args.size() == 2) {
                    startIdx = 1;  // 旧形式
                }

                for (size_t i = startIdx; i < callData.args.size(); ++i) {
                    auto value = convertOperand(*callData.args[i]);
                    auto hirType = getOperandType(*callData.args[i]);
                    currentStr = generateFormatReplace(currentStr, value, hirType);
                }

                formattedStr = currentStr;
            }

            // 出力
            auto printFunc = module->getOrInsertFunction(
                isNewline ? "cm_println_string" : "cm_print_string",
                llvm::FunctionType::get(ctx.getVoidType(), {ctx.getPtrType()}, false));
            builder->CreateCall(printFunc, {formattedStr});
        } else {
            // 最初の引数が文字列でない場合:全ての引数を連結
            llvm::Value* resultStr = builder->CreateGlobalStringPtr("", "concat_str");

            for (size_t i = 0; i < callData.args.size(); ++i) {
                auto value = convertOperand(*callData.args[i]);
                auto hirType = getOperandType(*callData.args[i]);
                auto valueStr = generateValueToString(value, hirType);

                auto concatFunc = module->getOrInsertFunction(
                    "cm_string_concat",
                    llvm::FunctionType::get(ctx.getPtrType(), {ctx.getPtrType(), ctx.getPtrType()},
                                            false));
                resultStr = builder->CreateCall(concatFunc, {resultStr, valueStr});
            }

            auto printFunc = module->getOrInsertFunction(
                isNewline ? "cm_println_string" : "cm_print_string",
                llvm::FunctionType::get(ctx.getVoidType(), {ctx.getPtrType()}, false));
            builder->CreateCall(printFunc, {resultStr});
        }
    } else {
        // std::cout << "[CODEGEN] generatePrintCall Single Arg\n" << std::flush;
        // 単一引数の場合
        auto arg = convertOperand(*callData.args[0]);
        // std::cout << "[CODEGEN] generatePrintCall Single Arg Operand Converted\n" << std::flush;
        auto argType = arg->getType();
        auto hirType = getOperandType(*callData.args[0]);

        if (argType->isPointerTy()) {
            // std::cout << "[CODEGEN] generatePrintCall Single Arg Pointer\n" << std::flush;
            auto printFunc = module->getOrInsertFunction(
                isNewline ? "cm_println_string" : "cm_print_string",
                llvm::FunctionType::get(ctx.getVoidType(), {ctx.getPtrType()}, false));
            // std::cout << "[CODEGEN] generatePrintCall Getting Func Decl Done\n" << std::flush;
            builder->CreateCall(printFunc, {arg});
            // std::cout << "[CODEGEN] generatePrintCall Call Created\n" << std::flush;
        } else if (argType->isIntegerTy()) {
            // std::cout << "[CODEGEN] generatePrintCall Single Arg Integer\n" << std::flush;
            auto intType = llvm::cast<llvm::IntegerType>(argType);
            bool isBoolType = hirType && hirType->kind == hir::TypeKind::Bool;
            bool isCharType = hirType && hirType->kind == hir::TypeKind::Char;
            bool isUnsigned =
                hirType &&
                (hirType->kind == hir::TypeKind::UTiny || hirType->kind == hir::TypeKind::UShort ||
                 hirType->kind == hir::TypeKind::UInt || hirType->kind == hir::TypeKind::ULong);

            if (isBoolType) {
                auto boolArg = arg;
                if (intType->getBitWidth() != 8) {
                    boolArg = builder->CreateTrunc(arg, ctx.getI8Type());
                }
                auto printBoolFunc = module->getOrInsertFunction(
                    isNewline ? "cm_println_bool" : "cm_print_bool",
                    llvm::FunctionType::get(ctx.getVoidType(), {ctx.getI8Type()}, false));
                builder->CreateCall(printBoolFunc, {boolArg});
            } else if (isCharType) {
                auto charArg = arg;
                if (intType->getBitWidth() != 8) {
                    charArg = builder->CreateTrunc(arg, ctx.getI8Type());
                }
                auto printCharFunc = module->getOrInsertFunction(
                    isNewline ? "cm_println_char" : "cm_print_char",
                    llvm::FunctionType::get(ctx.getVoidType(), {ctx.getI8Type()}, false));
                builder->CreateCall(printCharFunc, {charArg});
            } else {
                // 64bit整数はcm_println_long/cm_println_ulongを使用
                unsigned bits = intType->getBitWidth();
                bool isLong = (hirType && (hirType->kind == hir::TypeKind::Long ||
                                           hirType->kind == hir::TypeKind::ULong ||
                                           hirType->kind == hir::TypeKind::ISize ||
                                           hirType->kind == hir::TypeKind::USize)) ||
                              bits > 32;

                if (isLong) {
                    llvm::FunctionCallee printFunc;
                    if (isUnsigned) {
                        printFunc = module->getOrInsertFunction(
                            isNewline ? "cm_println_ulong" : "cm_print_ulong",
                            llvm::FunctionType::get(ctx.getVoidType(), {ctx.getI64Type()}, false));
                    } else {
                        printFunc = module->getOrInsertFunction(
                            isNewline ? "cm_println_long" : "cm_print_long",
                            llvm::FunctionType::get(ctx.getVoidType(), {ctx.getI64Type()}, false));
                    }
                    auto longArg = arg;
                    if (bits != 64) {
                        if (bits < 64) {
                            longArg = isUnsigned ? builder->CreateZExt(arg, ctx.getI64Type())
                                                 : builder->CreateSExt(arg, ctx.getI64Type());
                        } else {
                            longArg = builder->CreateTrunc(arg, ctx.getI64Type());
                        }
                    }
                    builder->CreateCall(printFunc, {longArg});
                } else {
                    llvm::FunctionCallee printFunc;
                    if (isUnsigned) {
                        printFunc = module->getOrInsertFunction(
                            isNewline ? "cm_println_uint" : "cm_print_uint",
                            llvm::FunctionType::get(ctx.getVoidType(), {ctx.getI32Type()}, false));
                    } else {
                        printFunc = module->getOrInsertFunction(
                            isNewline ? "cm_println_int" : "cm_print_int",
                            llvm::FunctionType::get(ctx.getVoidType(), {ctx.getI32Type()}, false));
                    }
                    auto intArg = arg;
                    if (bits < 32) {
                        intArg = isUnsigned ? builder->CreateZExt(arg, ctx.getI32Type())
                                            : builder->CreateSExt(arg, ctx.getI32Type());
                    }
                    builder->CreateCall(printFunc, {intArg});
                }
            }
        } else if (argType->isFloatingPointTy()) {
            auto printFunc = module->getOrInsertFunction(
                isNewline ? "cm_println_double" : "cm_print_double",
                llvm::FunctionType::get(ctx.getVoidType(), {ctx.getF64Type()}, false));

            auto doubleArg = arg;
            if (argType->isFloatTy()) {
                doubleArg = builder->CreateFPExt(arg, ctx.getF64Type());
            }
            builder->CreateCall(printFunc, {doubleArg});
        }
    }
}

}  // namespace cm::codegen::llvm_backend


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +73 to +91

## 3. `stoll: out of range` — 大きな16進リテラル

### 概要
`ulong`型に`0x8000000000000000`以上の値を代入すると、コンパイラ内部の`stoll`(signed long long変換)でオーバーフローエラーが発生する。

### 再現コード
```cm
ulong val = 0xFE6C6C0000000000; // ← stoll: out of range
```

### 回避策
ビット演算で構築する:
```cm
// 0xFE6C6C0000000000 を回避
ulong val = (0x7E6C6C0000000000) | (0x0080000000000000 << 1);
```
または、上位ビットが立たない値に設計を変更する。

Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation in bugs1.md states that hex literals >= 0x8000000000000000 cause a stoll overflow error (line 77). However, the code changes in lexer.cpp (lines 220-222) show that this bug has been fixed by using stoull instead of stoll. The bug should be marked as "状態: 修正済み" (Status: Fixed) rather than leaving it without a status, or the documentation should be updated to reflect that this is now resolved in v0.14.1.

Copilot uses AI. Check for mistakes.
Comment on lines +115 to +144
## Bug #4: 整数リテラルがi32に推論される(二項演算)

**発見日**: 2026-02-16
**状態**: 未修正(回避策あり)
**重要度**: 中

### 症状
`ulong` 変数と整数リテラルの二項演算で、リテラルが `i32` に推論され、LLVM IR生成時に型不一致エラーが発生。

```
// エラー: Both operands to a binary operator are not of the same type!
// %bitand = and i64 %load11, i32 63
ulong new_read = (read_idx + 1) & 63;
```

### 再現条件
- `ulong` 型変数との `&`(AND)演算に整数リテラルを直接使用
- UEFI(x86_64)ターゲット

### 回避策
整数リテラルを明示的に `ulong` にキャストする。

```
// 回避前: コンパイルエラー
ulong new_read = (read_idx + 1) & 63;

// 回避後: 明示的キャスト
ulong ring_mask = 63 as ulong;
ulong new_read = (read_idx + 1) & ring_mask;
```
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug #4 describes an issue where integer literals are inferred as i32 in binary operations with ulong, causing LLVM IR type mismatch errors. However, the code changes in this PR (src/codegen/llvm/core/operators.cpp lines 539-584) add type widening logic for bitwise operations that should resolve this exact issue by automatically extending the narrower operand to match the wider one. The bug status should be updated to "修正済み" (Fixed) or the documentation should clarify whether this fix fully addresses the reported issue.

Copilot uses AI. Check for mistakes.
// ============================================================
// 64-bit Integer Output (long / ulong)
// ============================================================
// wasm_int64_to_str はruntime_format.c内で定義されている(staticなので再宣言)
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states that wasm_int64_to_str is defined as static in runtime_format.c, but this function wasm_ulong_to_str is being defined here as static. This creates a local implementation that duplicates logic for unsigned long conversion. Consider either:

  1. Declaring wasm_int64_to_str as non-static in runtime_format.c and including a header declaration, or
  2. Moving wasm_ulong_to_str to runtime_format.c alongside wasm_int64_to_str for consistency
Suggested change
// wasm_int64_to_str はruntime_format.c内で定義されている(staticなので再宣言)
// Helper for converting unsigned long long to string for WASM output

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +104
find_library(LLVM_SHARED_LIB
NAMES LLVM LLVM-17
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CMakeLists.txt change hardcodes "LLVM-17" in the find_library NAMES parameter. This could cause issues when using LLVM 18, 16, 15, or 14, which are all listed as supported versions in CM_LLVM_SUPPORTED_VERSIONS. Consider making this version-agnostic by either:

  1. Using only "LLVM" without version suffix, or
  2. Dynamically constructing the name based on the found LLVM_PACKAGE_VERSION
Suggested change
find_library(LLVM_SHARED_LIB
NAMES LLVM LLVM-17
# LLVM_PACKAGE_VERSION からメジャーバージョンを取得し、バージョン付きライブラリ名を動的に構築する
if(LLVM_PACKAGE_VERSION)
string(REGEX MATCH "^[0-9]+" LLVM_VERSION_MAJOR "${LLVM_PACKAGE_VERSION}")
endif()
find_library(LLVM_SHARED_LIB
NAMES LLVM
LLVM-${LLVM_VERSION_MAJOR}

Copilot uses AI. Check for mistakes.
Comment on lines +282 to +287
// i32範囲外 → long(i64)
// 符号なし領域(MSBが立つ場合)はulongとして扱う
if (val < 0) {
// ビットキャストされた巨大unsigned値(例: 0xFE6C6C...)
constant.type = hir::make_ulong();
} else {
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for determining unsigned type when val is negative (lines 284-286) seems counterintuitive. When val is negative (after being bit-cast from a large unsigned hex literal like 0xFE6C6C...), it's treated as ulong. However, this only works correctly because of the bit-casting in the lexer where large hex values are converted via static_cast from uint64_t to int64_t. This creates a tight coupling between lexer behavior and this type inference logic. Consider adding a comment explaining this relationship or refactoring to make the intent clearer, perhaps by passing additional metadata about whether the literal was originally unsigned.

Suggested change
// i32範囲外 → long(i64)
// 符号なし領域(MSBが立つ場合)はulongとして扱う
if (val < 0) {
// ビットキャストされた巨大unsigned値(例: 0xFE6C6C...)
constant.type = hir::make_ulong();
} else {
// i32範囲外 → 64bit 整数リテラル
//
// 注意:
// 大きな 16 進整数リテラル(例: 0xFE6C6C...)は、lexer 側でいったん
// uint64_t として読み取り、そのビットパターンを static_cast で int64_t
// に詰め替えた値としてここに渡される。
// そのため「val < 0」のときは、元々のリテラルは 64bit 符号なし整数で
// MSB が立っていた(= 本来の unsigned 領域の値)ことを意味する。
// ここではその約束に依存して、負の値を ulong リテラルとして扱う。
// (lexer のビットキャストの仕様を変更する場合は、この分岐も要確認)
if (val < 0) {
constant.type = hir::make_ulong();
} else {
// i32 範囲外だが MSB は立っていない → 通常の long(i64)

Copilot uses AI. Check for mistakes.
Comment on lines +541 to +551
if (lhs->getType()->isIntegerTy() && rhs->getType()->isIntegerTy()) {
auto lhsBits = lhs->getType()->getIntegerBitWidth();
auto rhsBits = rhs->getType()->getIntegerBitWidth();
bool is_unsigned = isUnsignedType(result_type);
if (lhsBits < rhsBits) {
lhs = is_unsigned ? builder->CreateZExt(lhs, rhs->getType(), "zext")
: builder->CreateSExt(lhs, rhs->getType(), "sext");
} else if (rhsBits < lhsBits) {
rhs = is_unsigned ? builder->CreateZExt(rhs, lhs->getType(), "zext")
: builder->CreateSExt(rhs, lhs->getType(), "sext");
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bit operation type widening logic uses signedness from the result_type to determine whether to use zero extension or sign extension. However, for bitwise operations (AND, OR, XOR), the signedness shouldn't typically affect the bit pattern result for values that fit within the smaller type. Using signed extension for unsigned types or vice versa could lead to unexpected behavior. For example, if you have ulong & int_literal, and the int_literal is negative (which shouldn't happen but could with explicit casts), sign extension would propagate the sign bit. Consider whether zero extension should always be used for bitwise operations, or if the signedness check should be based on the operand types rather than result_type.

Copilot uses AI. Check for mistakes.
Comment on lines +610 to +621
// longがあればlong(unsigned区別: Bug2修正)
else if (lhs_type->kind == hir::TypeKind::Long ||
rhs_type->kind == hir::TypeKind::Long ||
lhs_type->kind == hir::TypeKind::ULong ||
rhs_type->kind == hir::TypeKind::ULong) {
result_type = hir::make_long();
// 片方でもULongならulong型を維持
if (lhs_type->kind == hir::TypeKind::ULong ||
rhs_type->kind == hir::TypeKind::ULong) {
result_type = hir::make_ulong();
} else {
result_type = hir::make_long();
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type promotion logic for long/ulong operations has an issue with mixing signed and unsigned operations. When one operand is Long and the other is ULong, the result is always ULong (lines 615-618). However, this may not be correct for all bitwise operations. For example, if we have long(-1) & ulong(0xFF), treating this as ulong may give different semantics than treating it as long. C/C++ has complex rules for usual arithmetic conversions that consider both size and signedness. Consider documenting this design decision or reviewing whether it matches the intended language semantics, especially for bitwise operations where sign semantics can be important.

Copilot uses AI. Check for mistakes.
修正内容:
- Bug#5: SCCP最適化がASM出力変数をwhile条件で定数扱いする問題を修正
- Bug#6: stollオーバーフロー → stoullに変更、大きな16進リテラル対応
- Bug#8: const式同士の加算が関数引数で正しく評価されない問題を修正
- Bug#9: 配列のvoid*キャストで配列コピー+バッファオーバーフローする問題を修正
  - パーサー優先順位、MIR array-to-pointer decay、LLVM alloca skipの3重修正
- Bug#10: implネストメソッド呼び出しでself変更が消失する問題を修正(JIT/LLVM)
  - expr_call.cpp: self書き戻しロジック修正
  - monomorphization_impl.cpp: ネストメソッドのself伝播修正

テスト追加:
- common/loops/while_sccp_regression(Bug#5回帰テスト)
- common/types/ulong_large_hex(Bug#6回帰テスト)
- common/const/const_expr_arithmetic(Bug#8回帰テスト)
- common/memory/array_ptr_cast(Bug#9回帰テスト)
- common/impl/impl_nested_self, impl_nested_self_deep, impl_ptr_self(Bug#10回帰テスト)
- common/impl/impl_ptr_nested(ポインタ経由implテスト)
- common/arrays/array_pointer_overlap(配列ポインタ重複テスト)
- common/asm/asm_no_inline(ASMインライン抑制テスト)
- common/types/union_const_type_sync(union const型同期テスト)

ドキュメント整理:
- bugs1-5.md を統合整理
  - cm_compiler_bugs_resolved.md(修正済みバグ)
  - cm_compiler_bugs_open.md(未修正バグ・制約事項)

テストインフラ:
- unified_test_runner.sh: 並列実行のジョブ管理を改善
  - 完了済みPIDの一括回収で空きスロットを即座に再利用
- JSバックエンド用skipファイル追加(void*使用テスト)
- WASMバックエンド用skipファイル追加(配列代入制限)

テスト結果: 全バックエンド 0 FAIL
- JIT(tip):  358 PASS / 0 FAIL
- LLVM(tlp): 391 PASS / 0 FAIL
- WASM(tlwp):356 PASS / 0 FAIL
- JS(tjp):   312 PASS / 0 FAIL
前回のBug#9/10修正で既に解決していた3つの問題を検証:
- 問題1: ポインタ経由impl呼出しでself書き戻し不在 → 修正済み
- 問題2: 構造体フィールド6個以上でGEPインデックスエラー → 修正済み
- 問題3: Load operand型不整合 → 修正済み

テスト追加:
- common/impl/impl_ptr_large_struct: 8フィールド構造体+ポインタ経由impl+ネストメソッド

テスト結果:
- JIT: 359 PASS / 0 FAIL
- LLVM: 392 PASS / 0 FAIL
Bug#1: 3引数関数でのポインタ破損(UEFI ABI不一致)
- Win64呼出規約がefi_mainのみだった → 全関数に適用

Bug#7: must { __asm__() } の制御フロー干渉
- 全ASMにhasSideEffects=trueとフラグクロバー設定

Bug#11: インライン展開によるASMレジスタ割当変更
Bug#12: インライン展開時のret先不在
- ASM含む関数にLLVM NoInline属性を付与

テスト結果:
- JIT: 359 PASS / 0 FAIL
- LLVM: 392 PASS / 0 FAIL
Bug#10修正: ポインタ経由のimplメソッド呼び出し(ptr->method())で、
small struct(<=16バイト)の場合にself引数が不正な値として解釈される
バグを修正。

根本原因:
- expr_call.cppのBug#10パスがポインタ変数を直接self引数として渡していた
- メソッド関数はstruct値を期待するため、ポインタ値が構造体データとして解釈
- getterが不正な値を返し、setterは書き戻しが行われなかった

修正内容:
1. expr_call.cpp: ptr->method()でDeref+Ref方式に変更
   - ポインタをDerefして構造体コピーを作成
   - コピーへの参照をself引数として渡す
2. monomorphization_impl.cpp: ネスト呼出しパスの上書き防止
   - Ref経由のポインタ型selfを再上書きしないようスキップ
3. monomorphization_impl.cpp: Derefパスの書き戻し追加
   - メソッド呼び出し後にDerefコピーをptr先に書き戻す

テスト: impl_ptr_small_struct テスト追加
検証: インタプリタ359/0, LLVM 392/0 (回帰なし)
Bug#10修正(self書き戻し不在)を検証する2つのテストを追加:

1. impl_ptr_writeback: setter→getter連鎖、元変数への反映、
   連続呼び出しの累積を検証
2. impl_ptr_single_field: 8バイト最小構造体(1フィールド)での
   getter/setter/double_it複合メソッドを検証

修正前はptr->get_x()がアドレスの一部を返し、
ptr->set_x()が元変数に反映されなかった。
問題: LLVMのO2パイプライン(インライン展開+DCE)がefi_mainのcall/ret命令を
削除し、フォールスルーによるUEFIクラッシュを引き起こしていた。
efi_mainの引数レジスタ(rcx=image_handle, rdx=system_table)が
インライン展開されたコードのself/引数として上書きされ、
UEFIデータ構造が破壊されていた。

修正:
- LLVMCodeGen::optimize()でUEFIターゲット時に全最適化をスキップ
- LLVMCodeGen::initialize()でUEFIターゲット時にCodeGenOptLevel=0を強制
- compileModules()でもUEFIターゲット時にPassBuilderスキップ(防御的修正)
- mir_to_llvm.cppでUEFI全関数にnoinline属性、efi_mainにoptnone属性を追加
- terminator.cppの一時デバッグ出力を削除
- expr_call.cpp: ptr->method()呼出後にderef_tempの内容を*ptrに書き戻す
  PtrWriteback構造体でderef_tempとptr_varのペアを記録し、
  Call terminator後のsuccess blockで書き戻しステートメントを挿入
- Bug#13をcm_compiler_bugs_resolved.mdに追加
- Bug#13回帰テスト(uefi_impl_inline)を追加
- impl_ptr系4テストにJSスキップファイルを追加(ポインタ非対応)

テスト結果:
  tip:  362 PASS / 0 FAIL / 4 SKIP
  tlp:  395 PASS / 0 FAIL / 7 SKIP
  tlwp: 360 PASS / 0 FAIL / 6 SKIP
- Makefile: SHORTCUT_TEMPLATEをtlw→twに変更(tw, twp, tw0-tw3, twp0-twp3)
- ヘルプテキスト更新
- README, QUICKSTART, DEVELOPMENT等のドキュメント参照を一括更新
- CIのYAML/shファイルには使用なし(変更不要)
- docs/archive/は履歴のため変更なし
- bugs2.md: Bug#10の詳細分析(impl selfの3つのサブ問題、検証マトリクス)
- bugs3.md: Bug#13の詳細分析(UEFIインライン展開によるレジスタ破壊)
- Makefile: SHORTCUT_TEMPLATEをtlw→twに変更(tw, twp, tw0-tw3, twp0-twp3)
- CI: ci.ymlのtlwp0/tlwp3をtwp0/twp3に修正
- ヘルプテキスト更新
- README, QUICKSTART, DEVELOPMENT等のドキュメント参照を一括更新
- docs/archive/は履歴のため変更なし
## 問題
Vec2[3] points = [...]; の後に points = [...]; で全体再代入すると
ゴミ値が出力される。初期化やプリミティブ配列の再代入は正常。

## 原因
代入式(HirBinaryOp::Assign)で右辺の配列リテラルを lower_expression
経由で一時変数に格納し、その全体をcopyする処理が、構造体要素を含む
配列で正しく動作しない。

## 修正
expr_ops.cpp: HirBinaryOp::Assignパスで右辺が配列リテラルの場合、
temp経由のcopyを避け、直接ターゲット変数の各インデックスに要素を
書き込むように変更。
stmt.cpp: lower_assignにも同様の処理を追加(フォールバック対応)。

## テスト
- JIT: 362 PASS / 0 FAIL
- LLVM: 395 PASS / 0 FAIL
- 新規テスト: struct_array_reassign.cm
以下の12パターンを網羅:
- ネスト2段構造体の初期化・再代入 (T1-T3)
- 配列フィールドの初期化・再代入 (T4-T5)
- 構造体配列の初期化・全体再代入 (T6-T7)
- ネスト構造体配列の初期化・全体再代入 (T8-T9)
- 配列フィールド構造体の再代入 (T10a-T10b)
- 3フィールド構造体配列の再代入 (T11)
- プリミティブ配列の再代入リグレッション確認 (T12)

JIT/LLVMバックエンド共に全パターン正常動作確認済み。
テスト合計: 364 PASS / 0 FAIL (368 Total)
## 修正内容

### Bug #15: 非export関数のexport関数からの呼出不可
- extract_exported_blocks()を修正し、非export関数定義もnamespace外に展開
- interface/struct/impl/enumブロック内の宣言は重複回避のためスキップ
- 変更: src/preprocessor/import.cpp

### Bug #16: &local as ulong キャスト型エラー
- parse_cast_expr()を新設し、asキャスト演算子の優先順位を修正
- main関数のretval型をi32に強制、0で初期化
- 変更: src/frontend/parser/parser_expr.cpp, parser.hpp, mir_to_llvm.cpp

## 全UEFIバグ検証結果
Bug #7,#9,#10,#13,#14,#17は以前の修正で解消済みを確認。
bugs4.md全バグを修正済みに更新。

## テスト結果
- make tip: 365 PASS / 0 FAIL
- make test-uefi: 9 PASS / 0 FAIL(新規3テスト追加)

## 新規テスト
- uefi_cross_module_call: クロスモジュール非export関数呼出(Bug#15)
- uefi_large_impl: 7フィールド構造体impl(Bug#10)
- uefi_stack_large: 大スタック+配列ポインタ(Bug#9/#17)
- ptr_to_int_cast: ポインタ→整数キャスト(Bug#16)
- Bug#17: UEFI関数にno-stack-arg-probe属性を追加し、___chkstk_msリンクエラーを防止
- 新規UEFIコンパイルテスト4件追加:
  - uefi_asm_while: Bug#5 ASM出力変数のwhile条件テスト
  - uefi_must_asm: Bug#7 must+ASM制御フローテスト
  - uefi_export_many: Bug#14 多数export関数ハング回帰テスト
  - uefi_stack_probe: Bug#17 大スタックフレーム(4KB/8KB超)テスト
- 全テスト0 FAIL確認済み (UEFI:13, JIT:365, LLVM:398, JS:314)
全バグが回避策なしで正常動作することを逆アセンブル解析で確認:
- Bug#5: ASM出力変数をwhile条件で直接使用 → uefi_bug5_direct.cm
- Bug#7: must { hlt } ループ → uefi_bug7_must_hlt.cm
- Bug#9: 配列アドレスをポインタに格納 → uefi_bug9_array_ptr.cm
- Bug#11: ASM関数の正常呼出 → uefi_bug11_asm_func.cm
- Bug#12: ret命令含むASM関数 → uefi_bug12_asm_ret.cm
- 全18件UEFIコンパイルテストPASS
- modified_localsにASMの出力制約(=r, +r)の変数を追加
- no_optフラグ付きステートメントのホイスト除外
- 回帰テスト追加: asm_licm_regression (3ケース)
- 全テスト通過: 406 Total / 399 PASS / 0 FAIL / 7 SKIP
- Bug#12: ret/iretを含むASMのoperand付き関数をmodule-level asmで生成
  - LLVM 17のnaked+call asm operand crashバグを回避
  - $N → 呼び出し規約レジスタ名に直接置換してmodule asmで出力
  - deleteBody()でLLVM関数を宣言のみに変換
- Bug#12: ret/iretを含むASMのoperandなし関数はNaked属性で生成
- Bug#11: UEFIターゲットでSystem V→Win64ccレジスタ自動リマップ
  - %rdi→%rcx, %rsi→%rdx の2段階プレースホルダー方式
- テスト更新: uefi_bug11/12テストを実用的な内容に刷新
- module-level asm方式を完全撤廃(syscall_entryラベル重複リグレッション解消)
- Naked属性をoperandの有無に関わらず常に付与
- operandあり時: $N→呼出規約レジスタ名に事前置換しoperandなしASMとして生成
  → LLVM17のnaked+operand crashを回避しつつprologue/epilogue除去
- クロバー制約をASMコード内のレジスタ使用から自動検出
- LLVM IR実験で確認: Win64ccはframe-pointer=noneでもprologue生成
  → Naked属性が唯一のprologue除去手段
- ABIレジスタ自動リマップを削除(${r:var}のスクラッチレジスタ用途を保護)
- Bug#7修正: must ASMにコンパイラバリア挿入(is_must限定、mfence不使用)
- Naked属性を純ASM関数のみに限定(ASM+Cm混在関数のGPFを解消)
- 回帰テスト3件追加: uefi_asm_scratch_reg, uefi_bug7_compiler_barrier, uefi_naked_mixed_func

検証結果: UEFIテスト21件PASS、カーネルテスト183件PASS / 0件FAIL
infer_binary/infer_unaryでis_numeric()チェック前に
resolve_typedef()を呼び出し、typedef型(MemAddr, IoPort等)の
加算・減算・ビット演算を許可する。

変更箇所:
- expr.cpp infer_binary(): ltype/rtypeのtypedef解決追加
- expr.cpp infer_unary(): otypeのtypedef解決追加

Cmテスト: 363 PASS / 0 FAIL
MIR lowering が typedef 引数(MemAddr=ulong等)のコピー用一時変数を
Pointer<primitive> 型で作成し、alloca ptr → load ptr → call @func(i64) の
型不整合が発生するバグを修正。

修正内容:
- terminator.cpp: call引数生成時に expectedType vs actualType の不整合を
  検出し、LoadInst の元 alloca から expectedType で再 load する処理を追加
- テスト追加: tests/uefi/static_typedef_call.cm
src/mir/nodes.hppでstd::unordered_mapを使用しているが<unordered_map>が
未インクルードだったため、Linux/GCCのCIでコンパイルエラーが発生していた。
macOSのAppleClangでは間接インクルードで解決されていたが、GCCでは厳密に
チェックされるため明示的なインクルードが必要。
- PR.mdをUEFIバグ17件修正・typedef算術演算・GCC CIビルド修正で更新
- v0.14.0.mdからv0.14.1内容を削除し元の状態に復元
- v0.14.1.mdを新規作成(独立したリリースノート)
- releases/index.md にv0.14.1エントリを追加
- tutorials/ja/index.md の対象バージョンをv0.14.1に更新
- tutorials/en/index.md の対象バージョンをv0.14.1に更新
- SKILL.md の機能対応表にUEFI安定化・typedef算術演算を追加
- リリースノートv0.14.1にバグ報告サマリーを統合(Bug#1-#17回帰テスト一覧、言語機能バグ一覧、既知制約セクション追加)
- READMEのテスト数をv0.14.1最新値に更新(JIT 347, LLVM 380, WASM 346, JS 306)
- 実装済みデザインドキュメント docs/design/v0.14.0,v0.14.1 を docs/archive/ に移動
- 英語チュートリアルにUEFIリンク追加、テストパス修正(test_programs→programs)
- PR.mdのtypedef_arithmeticテスト名をtypedef_compound_assignに修正
- チラシのバージョンをv0.14.1に更新
@shadowlink0122 shadowlink0122 merged commit a986423 into main Feb 20, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants