Conversation
- 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)
- 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)
There was a problem hiding this comment.
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.
|
|
||
| ## 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); | ||
| ``` | ||
| または、上位ビットが立たない値に設計を変更する。 | ||
|
|
There was a problem hiding this comment.
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.
docs/design/v0.14.1/bugs2.md
Outdated
| ## 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; | ||
| ``` |
There was a problem hiding this comment.
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.
| // ============================================================ | ||
| // 64-bit Integer Output (long / ulong) | ||
| // ============================================================ | ||
| // wasm_int64_to_str はruntime_format.c内で定義されている(staticなので再宣言) |
There was a problem hiding this comment.
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:
- Declaring wasm_int64_to_str as non-static in runtime_format.c and including a header declaration, or
- Moving wasm_ulong_to_str to runtime_format.c alongside wasm_int64_to_str for consistency
| // wasm_int64_to_str はruntime_format.c内で定義されている(staticなので再宣言) | |
| // Helper for converting unsigned long long to string for WASM output |
| find_library(LLVM_SHARED_LIB | ||
| NAMES LLVM LLVM-17 |
There was a problem hiding this comment.
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:
- Using only "LLVM" without version suffix, or
- Dynamically constructing the name based on the found LLVM_PACKAGE_VERSION
| 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} |
| // i32範囲外 → long(i64) | ||
| // 符号なし領域(MSBが立つ場合)はulongとして扱う | ||
| if (val < 0) { | ||
| // ビットキャストされた巨大unsigned値(例: 0xFE6C6C...) | ||
| constant.type = hir::make_ulong(); | ||
| } else { |
There was a problem hiding this comment.
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.
| // 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) |
| 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"); | ||
| } |
There was a problem hiding this comment.
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.
| // 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(); | ||
| } |
There was a problem hiding this comment.
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.
修正内容: - 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に更新
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件を全て修正しました。
utiny*デリファレンスのi32切り詰めint→utinyキャストの符号拡張ptr->method()のself書き戻し&local as ulongキャスト不正typedef算術演算サポート
typedef型の値に対する算術演算と、typedef引数のstatic→static関数呼び出し時の型不整合を修正しました。
整数型出力の完全対応
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.cppcm_println_long/ulong/uintとformat/to_string版追加src/codegen/llvm/wasm/runtime_print.ccm_println_long/ulong出力関数追加src/codegen/llvm/core/operators.cppsrc/mir/lowering/expr_call.cppsrc/mir/passes/interprocedural/inlining.cpp🎯 主要な変更
1. JSバックエンド大規模リファクタリング
JSコードジェネレータを大幅にリファクタリングし、1,600行以上の不要コードを削除しました。
JSテスト通過率
JSコンパイルの使い方
2. 演算子オーバーロード改善
impl T { operator ... }構文演算子を
impl T for InterfaceNameではなく、直接impl T { operator ... }で定義可能になりました。複合代入演算子
二項演算子を定義すると、対応する複合代入演算子が自動的に使えます。
サポートする複合代入演算子:
+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=ビット演算子オーバーロード
&,|,^,<<,>>の全ビット演算子をオーバーロード可能になりました。interface存在チェック
impl T for IのIが宣言済みinterfaceでない場合、コンパイルエラーになります。3. インラインユニオン型とnull型
インラインユニオン構文 (
int | null)typedefなしで直接ユニオン型を使用可能になりました。
null型追加TypeKind::Null、make_null()parse_type_with_union()4. プラットフォームディレクティブ
ファイル先頭に
//! platform:で実行可能なプラットフォームを制約可能。対応プラットフォーム:
native,js,wasm,uefi,baremetal5. ベアメタル / UEFIサポート
--target=uefiでUEFIアプリケーションをコンパイル可能。QEMUでHello World出力確認済み。libs/uefi/) を新規作成6. VSCode拡張機能の追加と改善
拡張機能の新規追加
Cm言語用VSCode拡張機能を新規作成しました。
cm.tmLanguage.json).cmファイルにCmアイコンを表示pnpm run packageでインストール可能なパッケージを生成TypeScript移行 + ESLint/Prettier導入
スクリプト(
update-version,verify-version)をJavaScript→TypeScriptに移行し、ESLint + Prettierで品質管理を自動化。tsconfig.json(strict, ES2020)pnpm run compileeslint.config.mjs(Flat Config v9+)pnpm run lint.prettierrcpnpm run format:checkCI統合
ci.ymlにextension-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コンパイラバグ + 言語機能)
mir_to_llvm.cpplicm.cppfolding.cpputiny*デリファレンスi32切り詰めmir_to_llvm.cppint→utinyキャスト不正mir_to_llvm.cppfolding.cppptr->method()のself書き戻しstmt.cpp(MIR)mir_to_llvm.cppmir_to_llvm.cppcodegen.cpp(native)mir_to_llvm.cppimport.cpp&local as ulongキャスト不正mir_to_llvm.cppcodegen.cpp(native)checking/expr.cppmonomorphization_impl.cpp<unordered_map>ヘッダー未インクルードnodes.hppexpr_call.cppoperators.cppinlining.cppbuiltins.cpp,runtime_print.cv0.14.0 修正
intに設定expr_call.cppi32にハードコードmir_to_llvm.cppmax_payload_size()がStruct型をデフォルト8バイトで計算types.cpp🔧 ビルド・テスト改善
JSスキップファイル整理
asm/io/net/sync/thread/CI改善
テスト構成再編成
tests/test_programs→tests/programsにリネームlibs/配下にプラットフォーム別で再構成📁 変更ファイル一覧
JSバックエンド
src/codegen/js/codegen.cppsrc/codegen/js/emit_expressions.cppsrc/codegen/js/emit_statements.cppsrc/codegen/js/builtins.hppsrc/codegen/js/runtime.hppsrc/codegen/js/types.hppLLVMバックエンド/MIR修正
src/codegen/llvm/core/types.cppsrc/codegen/llvm/core/mir_to_llvm.cppsrc/codegen/llvm/native/codegen.cppsrc/mir/lowering/expr_call.cppsrc/mir/lowering/stmt.cppsrc/mir/lowering/monomorphization_impl.cppsrc/mir/lowering/impl.cppsrc/mir/passes/scalar/folding.cppsrc/mir/passes/loop/licm.cppsrc/mir/nodes.hppsrc/frontend/types/checking/expr.cppsrc/preprocessor/import.cpp型チェッカー/パーサー
src/frontend/parser/parser.hppimpl T { operator ... }構文、インラインユニオン型src/frontend/types/checking/expr.cppsrc/frontend/types/checking/decl.cppVSCode拡張機能
vscode-extension/vscode-extension/syntaxes/cm.tmLanguage.jsonvscode-extension/scripts/*.tsvscode-extension/eslint.config.mjsvscode-extension/.prettierrcvscode-extension/tsconfig.json.github/workflows/ci.ymlチュートリアル・ドキュメント
docs/tutorials/ja/advanced/operators.mddocs/tutorials/en/advanced/operators.mddocs/tutorials/ja/basics/operators.mddocs/tutorials/ja/basics/setup.mddocs/tutorials/ja/compiler/uefi.mddocs/tutorials/ja/compiler/js-compilation.mddocs/releases/v0.14.0.mddocs/QUICKSTART.mdvscode-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.*tests/programs/enum/associated_data.*tests/programs/asm/.skip等tests/unified_test_runner.shtests/programs/uefi/uefi_compile/*tests/programs/baremetal/allowed/*tests/programs/common/types/ptr_to_int_cast.*tests/programs/common/types/typedef_compound_assign.*サンプル
examples/web-app/examples/uefi/🧪 テスト状況
📊 統計
✅ チェックリスト
make tip全テスト通過(347 PASS / 0 FAIL)make tlp全テスト通過(380 PASS / 0 FAIL)make tw全テスト通過(346 PASS / 0 FAIL)make tjp全テスト通過(306 PASS / 0 FAIL)docs/releases/v0.14.0.md)リリース日: 2026年2月19日
バージョン: v0.14.1