diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-22 15:33:17 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-22 15:35:05 +0000 |
| commit | 8745f421b4cf7e1d54c17a360d46fab889ca12cc (patch) | |
| tree | 4c2a3508627db04493af35643580417d41b63f89 /lib | |
| parent | 5e84edbe1a79c6a7752ee276ea6c1a4daef4fa8e (diff) | |
feat(input)!: validate INPUT and read whole lines into STRING
Scalar INPUT now reads one whitespace-delimited token through module-local
IR helpers instead of scanf, then skips spaces and tabs and consumes one
newline. INTEGER, REAL, BOOLEAN (TRUE or FALSE in any case) and CHAR are
validated, and bad input or end of input is a runtime error. STRING INPUT
reads the rest of the line. The unused scanf and REAL printf format globals
are removed.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/CodeGen/CTypeGen.cc | 17 | ||||
| -rw-r--r-- | lib/CodeGen/CodeGen.cc | 7 | ||||
| -rw-r--r-- | lib/CodeGen/InputGen.cc | 328 | ||||
| -rw-r--r-- | lib/CodeGen/NativeTypeSystem.cc | 34 | ||||
| -rw-r--r-- | lib/CodeGen/StatementGen.cc | 30 |
5 files changed, 351 insertions, 65 deletions
diff --git a/lib/CodeGen/CTypeGen.cc b/lib/CodeGen/CTypeGen.cc index 18326cb..90f0aa3 100644 --- a/lib/CodeGen/CTypeGen.cc +++ b/lib/CodeGen/CTypeGen.cc @@ -186,23 +186,10 @@ void CodeGen::emitCOutput(Value *Val, const TypeInfo *Info, bool AppendNewline) Builder->CreateCall(PrintfFunc, {Fmt, Val}); } -void CodeGen::emitCInput(Value *Addr, const TypeInfo *Info) { +void CodeGen::emitCInput(Value *Addr, const TypeInfo *Info, int Line) { if (Info->Kind != TypeKind::CScalar || Info->isVoid()) { reportError("INPUT is not supported for C pointer or void type %s", Info->Name.c_str()); return; } - if (Info->isBoolean()) { - const TypeInfo *NativeInt = resolveType("C_INT"); - AllocaInst *Temp = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(), - NativeInt->LLVMType, "c_bool_input"); - Builder->CreateStore(Types->getZeroValue("C_INT"), Temp); - Value *Fmt = Builder->CreateGlobalString("%d", "fmt_c_input_bool", 0, TheModule.get()); - Builder->CreateCall(ScanfFunc, {Fmt, Temp}); - Value *Input = Builder->CreateLoad(NativeInt->LLVMType, Temp); - Value *Bool = convertNumericValue(Input, NativeInt, Info); - Builder->CreateStore(Bool, Addr); - return; - } - Value *Fmt = Builder->CreateGlobalString(Info->CInputFormat, "fmt_c_input", 0, TheModule.get()); - Builder->CreateCall(ScanfFunc, {Fmt, Addr}); + emitScalarInput(Addr, Info, Line); } diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index 772dc82..64ed7c4 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -94,17 +94,10 @@ void CodeGen::SetupExternalFunctions() { FunctionType *PrintfType = FunctionType::get(Type::getInt32Ty(*TheContext), PrintfArgs, true); PrintfFunc = TheModule->getOrInsertFunction("printf", PrintfType); - FunctionType *ScanfType = FunctionType::get(Type::getInt32Ty(*TheContext), PrintfArgs, true); - ScanfFunc = TheModule->getOrInsertFunction("scanf", ScanfType); - PrintfFormatStr = Builder->CreateGlobalString("%lld\n", "fmt_nl", 0, TheModule.get()); - PrintfFloatFormatStr = Builder->CreateGlobalString("%f\n", "fmt_flt", 0, TheModule.get()); PrintfStringFormatStr = Builder->CreateGlobalString("%s\n", "fmt_str", 0, TheModule.get()); PrintfCharFormatStr = Builder->CreateGlobalString("%c\n", "fmt_chr", 0, TheModule.get()); - ScanfFormatStr = Builder->CreateGlobalString("%lld", "fmt_in", 0, TheModule.get()); - ScanfFloatFormatStr = Builder->CreateGlobalString("%lf", "fmt_in_flt", 0, TheModule.get()); - TrueStr = Builder->CreateGlobalString("TRUE", "str_true", 0, TheModule.get()); FalseStr = Builder->CreateGlobalString("FALSE", "str_false", 0, TheModule.get()); EmptyStringStr = Builder->CreateGlobalString("", "str_empty", 0, TheModule.get()); diff --git a/lib/CodeGen/InputGen.cc b/lib/CodeGen/InputGen.cc new file mode 100644 index 0000000..349e7ce --- /dev/null +++ b/lib/CodeGen/InputGen.cc @@ -0,0 +1,328 @@ +#include "cps/CodeGen.h" +#include "cps/RuntimeCheck.h" +#include "llvm/IR/Intrinsics.h" + +using namespace llvm; +using namespace cps; + +namespace { +// Defines (once per module) `ptr __cps_input_token()`: reads one whitespace-delimited token from +// stdin into a malloc'd NUL-terminated buffer, or returns null at end of input. After the token it +// skips spaces, tabs and carriage returns and consumes one newline if it follows, so the next +// INPUT of a STRING starts on the following line. The caller frees the buffer. +FunctionCallee getInputTokenHelper(Module &M) { + LLVMContext &Ctx = M.getContext(); + Type *I8 = Type::getInt8Ty(Ctx), *I32 = Type::getInt32Ty(Ctx), *I64 = Type::getInt64Ty(Ctx); + PointerType *Ptr = PointerType::getUnqual(Ctx); + FunctionType *HelperTy = FunctionType::get(Ptr, {}, false); + if (Function *Existing = M.getFunction("__cps_input_token")) return {HelperTy, Existing}; + + FunctionCallee Getchar = M.getOrInsertFunction("getchar", FunctionType::get(I32, {}, false)); + FunctionCallee Ungetc = M.getOrInsertFunction("ungetc", FunctionType::get(I32, {I32, Ptr}, false)); + FunctionCallee Isspace = M.getOrInsertFunction("isspace", FunctionType::get(I32, {I32}, false)); + FunctionCallee Realloc = M.getOrInsertFunction("realloc", FunctionType::get(Ptr, {Ptr, I64}, false)); + + Function *F = Function::Create(HelperTy, Function::InternalLinkage, "__cps_input_token", &M); + BasicBlock *Entry = BasicBlock::Create(Ctx, "entry", F); + BasicBlock *SkipHead = BasicBlock::Create(Ctx, "skip.head", F); + BasicBlock *SkipTest = BasicBlock::Create(Ctx, "skip.test", F); + BasicBlock *Append = BasicBlock::Create(Ctx, "append", F); + BasicBlock *Grow = BasicBlock::Create(Ctx, "grow", F); + BasicBlock *Store = BasicBlock::Create(Ctx, "store", F); + BasicBlock *ReadTest = BasicBlock::Create(Ctx, "read.test", F); + BasicBlock *Finish = BasicBlock::Create(Ctx, "finish", F); + BasicBlock *TrailHead = BasicBlock::Create(Ctx, "trail.head", F); + BasicBlock *TrailSkip = BasicBlock::Create(Ctx, "trail.skip", F); + BasicBlock *TrailEnd = BasicBlock::Create(Ctx, "trail.end", F); + BasicBlock *PushBack = BasicBlock::Create(Ctx, "pushback", F); + BasicBlock *Done = BasicBlock::Create(Ctx, "done", F); + BasicBlock *Eof = BasicBlock::Create(Ctx, "eof", F); + + IRBuilder<> B(Entry); + Value *Buf = B.CreateAlloca(Ptr, nullptr, "buf"); + Value *Len = B.CreateAlloca(I64, nullptr, "len"); + Value *Cap = B.CreateAlloca(I64, nullptr, "cap"); + Value *Cur = B.CreateAlloca(I32, nullptr, "cur"); + B.CreateStore(ConstantPointerNull::get(Ptr), Buf); + B.CreateStore(ConstantInt::get(I64, 0), Len); + B.CreateStore(ConstantInt::get(I64, 0), Cap); + B.CreateBr(SkipHead); + + // Leading whitespace. + B.SetInsertPoint(SkipHead); + Value *First = B.CreateCall(Getchar); + B.CreateStore(First, Cur); + B.CreateCondBr(B.CreateICmpEQ(First, ConstantInt::get(I32, -1)), Eof, SkipTest); + B.SetInsertPoint(SkipTest); + Value *IsSpace = B.CreateICmpNE(B.CreateCall(Isspace, {First}), ConstantInt::get(I32, 0)); + B.CreateCondBr(IsSpace, SkipHead, Append); + + // Append Cur to the buffer, keeping room for the byte and the terminator. + B.SetInsertPoint(Append); + Value *L = B.CreateLoad(I64, Len), *C = B.CreateLoad(I64, Cap); + B.CreateCondBr(B.CreateICmpUGE(B.CreateAdd(L, ConstantInt::get(I64, 2)), C), Grow, Store); + B.SetInsertPoint(Grow); + Value *NewCap = B.CreateSelect(B.CreateICmpEQ(C, ConstantInt::get(I64, 0)), + ConstantInt::get(I64, 32), B.CreateMul(C, ConstantInt::get(I64, 2))); + B.CreateStore(B.CreateCall(Realloc, {B.CreateLoad(Ptr, Buf), NewCap}), Buf); + B.CreateStore(NewCap, Cap); + B.CreateBr(Store); + B.SetInsertPoint(Store); + Value *StoreLen = B.CreateLoad(I64, Len); + Value *Slot = B.CreateGEP(I8, B.CreateLoad(Ptr, Buf), {StoreLen}); + B.CreateStore(B.CreateTrunc(B.CreateLoad(I32, Cur), I8), Slot); + B.CreateStore(B.CreateAdd(StoreLen, ConstantInt::get(I64, 1)), Len); + Value *Next = B.CreateCall(Getchar); + B.CreateStore(Next, Cur); + B.CreateCondBr(B.CreateICmpEQ(Next, ConstantInt::get(I32, -1)), Finish, ReadTest); + B.SetInsertPoint(ReadTest); + Value *NextSpace = B.CreateICmpNE(B.CreateCall(Isspace, {Next}), ConstantInt::get(I32, 0)); + B.CreateCondBr(NextSpace, Finish, Append); + + // Terminate, then skip trailing spaces/tabs/CRs and one newline. + B.SetInsertPoint(Finish); + Value *EndLen = B.CreateLoad(I64, Len); + B.CreateStore(ConstantInt::get(I8, 0), B.CreateGEP(I8, B.CreateLoad(Ptr, Buf), {EndLen})); + B.CreateBr(TrailHead); + B.SetInsertPoint(TrailHead); + Value *Ch = B.CreateLoad(I32, Cur); + Value *Blank = B.CreateOr(B.CreateOr(B.CreateICmpEQ(Ch, ConstantInt::get(I32, ' ')), + B.CreateICmpEQ(Ch, ConstantInt::get(I32, '\t'))), + B.CreateICmpEQ(Ch, ConstantInt::get(I32, '\r'))); + B.CreateCondBr(Blank, TrailSkip, TrailEnd); + B.SetInsertPoint(TrailSkip); + B.CreateStore(B.CreateCall(Getchar), Cur); + B.CreateBr(TrailHead); + B.SetInsertPoint(TrailEnd); + Value *End = B.CreateLoad(I32, Cur); + Value *KeepsNothing = B.CreateOr(B.CreateICmpEQ(End, ConstantInt::get(I32, '\n')), + B.CreateICmpEQ(End, ConstantInt::get(I32, -1))); + B.CreateCondBr(KeepsNothing, Done, PushBack); + B.SetInsertPoint(PushBack); + // The stdin FILE* is a platform-specific symbol; host and target match. +#if defined(_WIN32) + FunctionCallee IobFunc = M.getOrInsertFunction("__acrt_iob_func", FunctionType::get(Ptr, {I32}, false)); + Value *Stdin = B.CreateCall(IobFunc, {ConstantInt::get(I32, 0)}); +#else +#if defined(__APPLE__) + const char *StdinName = "__stdinp"; +#else + const char *StdinName = "stdin"; +#endif + GlobalVariable *StdinVar = M.getGlobalVariable(StdinName); + if (!StdinVar) + StdinVar = new GlobalVariable(M, Ptr, false, GlobalValue::ExternalLinkage, nullptr, StdinName); + Value *Stdin = B.CreateLoad(Ptr, StdinVar); +#endif + B.CreateCall(Ungetc, {End, Stdin}); + B.CreateBr(Done); + B.SetInsertPoint(Done); + B.CreateRet(B.CreateLoad(Ptr, Buf)); + B.SetInsertPoint(Eof); + B.CreateRet(ConstantPointerNull::get(Ptr)); + return {HelperTy, F}; +} + +// Defines `i1 __cps_parse_int(ptr token, ptr out)`: an optionally signed decimal integer that fills +// the whole token and fits in INTEGER (what strtoll accepts in base 10, without errno). +FunctionCallee getParseIntHelper(Module &M) { + LLVMContext &Ctx = M.getContext(); + Type *I1 = Type::getInt1Ty(Ctx), *I8 = Type::getInt8Ty(Ctx), *I64 = Type::getInt64Ty(Ctx); + PointerType *Ptr = PointerType::getUnqual(Ctx); + FunctionType *HelperTy = FunctionType::get(I1, {Ptr, Ptr}, false); + if (Function *Existing = M.getFunction("__cps_parse_int")) return {HelperTy, Existing}; + + Function *F = Function::Create(HelperTy, Function::InternalLinkage, "__cps_parse_int", &M); + Value *Token = F->getArg(0), *Out = F->getArg(1); + BasicBlock *Entry = BasicBlock::Create(Ctx, "entry", F); + BasicBlock *Head = BasicBlock::Create(Ctx, "head", F), *Body = BasicBlock::Create(Ctx, "body", F); + BasicBlock *Tail = BasicBlock::Create(Ctx, "tail", F), *Negate = BasicBlock::Create(Ctx, "negate", F); + BasicBlock *Finish = BasicBlock::Create(Ctx, "finish", F), *Fail = BasicBlock::Create(Ctx, "fail", F); + + IRBuilder<> B(Entry); + Value *Acc = B.CreateAlloca(I64, nullptr, "acc"); + Value *Pos = B.CreateAlloca(Ptr, nullptr, "pos"); + Value *Sign = B.CreateLoad(I8, Token); + Value *IsNeg = B.CreateICmpEQ(Sign, ConstantInt::get(I8, '-')); + Value *HasSign = B.CreateOr(IsNeg, B.CreateICmpEQ(Sign, ConstantInt::get(I8, '+'))); + Value *Start = B.CreateGEP(I8, Token, {B.CreateZExt(HasSign, I64)}); + B.CreateStore(ConstantInt::get(I64, 0), Acc); + B.CreateStore(Start, Pos); + B.CreateBr(Head); + + // Accumulate negatively so INTEGER's minimum is representable. + B.SetInsertPoint(Head); + Value *P = B.CreateLoad(Ptr, Pos); + Value *Digit = B.CreateSub(B.CreateLoad(I8, P), ConstantInt::get(I8, '0')); + B.CreateCondBr(B.CreateICmpULT(Digit, ConstantInt::get(I8, 10)), Body, Tail); + B.SetInsertPoint(Body); + Value *Old = B.CreateLoad(I64, Acc); + Value *Mul = B.CreateBinaryIntrinsic(Intrinsic::smul_with_overflow, Old, ConstantInt::get(I64, 10)); + Value *Sub = B.CreateBinaryIntrinsic(Intrinsic::ssub_with_overflow, B.CreateExtractValue(Mul, 0), + B.CreateZExt(Digit, I64)); + Value *Overflow = B.CreateOr(B.CreateExtractValue(Mul, 1), B.CreateExtractValue(Sub, 1)); + B.CreateStore(B.CreateExtractValue(Sub, 0), Acc); + B.CreateStore(B.CreateGEP(I8, P, {ConstantInt::get(I64, 1)}), Pos); + BasicBlock *Next = BasicBlock::Create(Ctx, "next", F); + B.CreateCondBr(Overflow, Fail, Next); + B.SetInsertPoint(Next); + B.CreateBr(Head); + + B.SetInsertPoint(Tail); + Value *AtEnd = B.CreateICmpEQ(B.CreateLoad(I8, P), ConstantInt::get(I8, 0)); + Value *HasDigits = B.CreateICmpNE(P, Start); + BasicBlock *Sign2 = BasicBlock::Create(Ctx, "sign", F); + B.CreateCondBr(B.CreateAnd(AtEnd, HasDigits), Sign2, Fail); + B.SetInsertPoint(Sign2); + Value *Magnitude = B.CreateLoad(I64, Acc); + B.CreateCondBr(IsNeg, Finish, Negate); + B.SetInsertPoint(Negate); + // +x needs -acc, which overflows only for INTEGER's minimum. + BasicBlock *Positive = BasicBlock::Create(Ctx, "positive", F); + B.CreateCondBr(B.CreateICmpEQ(Magnitude, ConstantInt::get(I64, APInt::getSignedMinValue(64))), Fail, Positive); + B.SetInsertPoint(Positive); + B.CreateStore(B.CreateNeg(Magnitude), Out); + B.CreateRet(ConstantInt::getTrue(Ctx)); + B.SetInsertPoint(Finish); + B.CreateStore(Magnitude, Out); + B.CreateRet(ConstantInt::getTrue(Ctx)); + B.SetInsertPoint(Fail); + B.CreateRet(ConstantInt::getFalse(Ctx)); + return {HelperTy, F}; +} + +// Defines `i1 __cps_parse_real(ptr token, ptr out)`: the rule of parseNumber in pseudocode-lib/ +// string.cc (shared by IS_NUM and STR_TO_NUM): strtod must consume the whole token and must not +// overflow to infinity. A finite overflow is told from an "inf" literal by the missing letter i. +FunctionCallee getParseRealHelper(Module &M) { + LLVMContext &Ctx = M.getContext(); + Type *I1 = Type::getInt1Ty(Ctx), *I8 = Type::getInt8Ty(Ctx), *I32 = Type::getInt32Ty(Ctx); + Type *DoubleTy = Type::getDoubleTy(Ctx); + PointerType *Ptr = PointerType::getUnqual(Ctx); + FunctionType *HelperTy = FunctionType::get(I1, {Ptr, Ptr}, false); + if (Function *Existing = M.getFunction("__cps_parse_real")) return {HelperTy, Existing}; + + FunctionCallee Strtod = M.getOrInsertFunction("strtod", FunctionType::get(DoubleTy, {Ptr, Ptr}, false)); + FunctionCallee Strchr = M.getOrInsertFunction("strchr", FunctionType::get(Ptr, {Ptr, I32}, false)); + Function *F = Function::Create(HelperTy, Function::InternalLinkage, "__cps_parse_real", &M); + Value *Token = F->getArg(0), *Out = F->getArg(1); + IRBuilder<> B(BasicBlock::Create(Ctx, "entry", F)); + Value *EndSlot = B.CreateAlloca(Ptr, nullptr, "end"); + Value *Number = B.CreateCall(Strtod, {Token, EndSlot}); + Value *Consumed = B.CreateICmpEQ(B.CreateLoad(I8, B.CreateLoad(Ptr, EndSlot)), ConstantInt::get(I8, 0)); + Value *Progress = B.CreateICmpNE(B.CreateLoad(Ptr, EndSlot), Token); + Value *IsInf = B.CreateFCmpOEQ(B.CreateUnaryIntrinsic(Intrinsic::fabs, Number), + ConstantFP::getInfinity(DoubleTy)); + Value *NoLowerI = B.CreateIsNull(B.CreateCall(Strchr, {Token, ConstantInt::get(I32, 'i')})); + Value *NoUpperI = B.CreateIsNull(B.CreateCall(Strchr, {Token, ConstantInt::get(I32, 'I')})); + Value *Overflowed = B.CreateAnd(IsInf, B.CreateAnd(NoLowerI, NoUpperI)); + B.CreateStore(Number, Out); + B.CreateRet(B.CreateAnd(B.CreateAnd(Consumed, Progress), B.CreateNot(Overflowed))); + return {HelperTy, F}; +} + +// Defines `i32 __cps_parse_bool(ptr token)`: 1 for TRUE, 0 for FALSE in any case, -1 otherwise. +FunctionCallee getParseBoolHelper(Module &M) { + LLVMContext &Ctx = M.getContext(); + Type *I8 = Type::getInt8Ty(Ctx), *I32 = Type::getInt32Ty(Ctx), *I64 = Type::getInt64Ty(Ctx); + PointerType *Ptr = PointerType::getUnqual(Ctx); + FunctionType *HelperTy = FunctionType::get(I32, {Ptr}, false); + if (Function *Existing = M.getFunction("__cps_parse_bool")) return {HelperTy, Existing}; + + FunctionCallee Strlen = M.getOrInsertFunction("strlen", FunctionType::get(I64, {Ptr}, false)); + Function *F = Function::Create(HelperTy, Function::InternalLinkage, "__cps_parse_bool", &M); + Value *Token = F->getArg(0); + BasicBlock *Entry = BasicBlock::Create(Ctx, "entry", F); + BasicBlock *IsTrue = BasicBlock::Create(Ctx, "true", F), *IsFalse = BasicBlock::Create(Ctx, "false", F); + BasicBlock *Other = BasicBlock::Create(Ctx, "other", F), *Not4 = BasicBlock::Create(Ctx, "not4", F); + IRBuilder<> B(Entry); + Value *Length = B.CreateCall(Strlen, {Token}); + // ORing 0x20 folds ASCII upper case to lower case; no other byte folds onto a letter. + auto Matches = [&](const char *Word) { + Value *All = ConstantInt::getTrue(Ctx); + for (unsigned I = 0; Word[I]; ++I) { + Value *Byte = B.CreateOr(B.CreateLoad(I8, B.CreateGEP(I8, Token, {ConstantInt::get(I64, I)})), + ConstantInt::get(I8, 0x20)); + All = B.CreateAnd(All, B.CreateICmpEQ(Byte, ConstantInt::get(I8, Word[I]))); + } + return All; + }; + BasicBlock *Check4 = BasicBlock::Create(Ctx, "check4", F), *Check5 = BasicBlock::Create(Ctx, "check5", F); + B.CreateCondBr(B.CreateICmpEQ(Length, ConstantInt::get(I64, 4)), Check4, Not4); + B.SetInsertPoint(Check4); + B.CreateCondBr(Matches("true"), IsTrue, Other); + B.SetInsertPoint(Not4); + B.CreateCondBr(B.CreateICmpEQ(Length, ConstantInt::get(I64, 5)), Check5, Other); + B.SetInsertPoint(Check5); + B.CreateCondBr(Matches("false"), IsFalse, Other); + B.SetInsertPoint(IsTrue); + B.CreateRet(ConstantInt::get(I32, 1)); + B.SetInsertPoint(IsFalse); + B.CreateRet(ConstantInt::get(I32, 0)); + B.SetInsertPoint(Other); + B.CreateRet(ConstantInt::get(I32, -1)); + return {HelperTy, F}; +} +} // namespace + +// Reads one token from stdin and stores it, validated, at Addr. A malformed token or the end of +// input is a runtime error. C scalars follow the same token model; integers must fit the C type. +void CodeGen::emitScalarInput(Value *Addr, const TypeInfo *Info, int Line) { + Module &M = *TheModule; + Type *I32 = Type::getInt32Ty(*TheContext), *I64 = Type::getInt64Ty(*TheContext); + Type *DoubleTy = Type::getDoubleTy(*TheContext); + PointerType *Ptr = PointerType::getUnqual(*TheContext); + FunctionCallee Free = M.getOrInsertFunction("free", FunctionType::get(Type::getVoidTy(*TheContext), {Ptr}, false)); + FunctionCallee Strlen = M.getOrInsertFunction("strlen", FunctionType::get(I64, {Ptr}, false)); + + Value *Token = Builder->CreateCall(getInputTokenHelper(M), {}, "input_token"); + RuntimeChecker->emitCheck(Builder->CreateIsNull(Token), + Builder->CreateGlobalString("[Fatal] line %d: INPUT reached the end of input\n", "err_input_eof", 0, &M), + {RuntimeChecker->lineValue(Line)}); + Value *TypeName = Builder->CreateGlobalString(Info->Name, "input_type_name", 0, &M); + Value *InvalidMsg = Builder->CreateGlobalString("[Fatal] line %d: Invalid %s input '%s'\n", + "err_input_invalid", 0, &M); + auto Require = [&](Value *Valid) { + RuntimeChecker->emitCheck(Builder->CreateNot(Valid), InvalidMsg, + {RuntimeChecker->lineValue(Line), TypeName, Token}); + }; + Function *Fn = Builder->GetInsertBlock()->getParent(); + + if (Info->isChar()) { + Require(Builder->CreateICmpEQ(Builder->CreateCall(Strlen, {Token}), ConstantInt::get(I64, 1))); + Builder->CreateStore(Builder->CreateLoad(Info->LLVMType, Token), Addr); + } else if (Info->isBoolean() && !Info->isC()) { + Value *Parsed = Builder->CreateCall(getParseBoolHelper(M), {Token}); + Require(Builder->CreateICmpSGE(Parsed, ConstantInt::get(I32, 0))); + Builder->CreateStore(Builder->CreateICmpNE(Parsed, ConstantInt::get(I32, 0)), Addr); + } else if (Info->isReal()) { + AllocaInst *Slot = CreateEntryBlockAlloca(Fn, DoubleTy, "input_real"); + Require(Builder->CreateCall(getParseRealHelper(M), {Token, Slot})); + Value *Number = Builder->CreateLoad(DoubleTy, Slot); + Type *Target = Info->LLVMType; + if (Target->isFloatTy()) Number = Builder->CreateFPTrunc(Number, Target); + else if (Target != DoubleTy) Number = Builder->CreateFPExt(Number, Target); + Builder->CreateStore(Number, Addr); + } else { + AllocaInst *Slot = CreateEntryBlockAlloca(Fn, I64, "input_int"); + Require(Builder->CreateCall(getParseIntHelper(M), {Token, Slot})); + Value *Number = Builder->CreateLoad(I64, Slot); + if (Info->isBoolean()) { + Builder->CreateStore(Builder->CreateICmpNE(Number, ConstantInt::get(I64, 0)), Addr); + } else { + unsigned Bits = Info->LLVMType->getIntegerBitWidth(); + Value *Stored = Number; + if (Bits < 64) { + Type *Narrow = Info->LLVMType; + Stored = Builder->CreateTrunc(Number, Narrow); + Value *Back = Info->isC() && !Info->CSigned ? Builder->CreateZExt(Stored, I64) + : Builder->CreateSExt(Stored, I64); + Require(Builder->CreateICmpEQ(Back, Number)); + } + if (Info->isC() && !Info->CSigned) + Require(Builder->CreateICmpSGE(Number, ConstantInt::get(I64, 0))); + Builder->CreateStore(Stored, Addr); + } + } + Builder->CreateCall(Free, {Token}); +} diff --git a/lib/CodeGen/NativeTypeSystem.cc b/lib/CodeGen/NativeTypeSystem.cc index d5d1c93..c1be163 100644 --- a/lib/CodeGen/NativeTypeSystem.cc +++ b/lib/CodeGen/NativeTypeSystem.cc @@ -45,7 +45,7 @@ void TypeSystem::registerBuiltins() { // of assumed LP64/LLP64 widths. LLVM's addressable bytes are eight bits. static_assert(CHAR_BIT == 8, "LLVM requires eight-bit addressable bytes"); auto Integral = [&](auto Sample, const char *Name, unsigned Rank, - const char *InputFormat, const char *OutputFormat) { + const char *OutputFormat) { using T = decltype(Sample); TypeInfo Info; Info.Name = cTypeName(Name); @@ -54,36 +54,34 @@ void TypeSystem::registerBuiltins() { Info.LLVMType = Type::getIntNTy(Context, sizeof(T) * CHAR_BIT); Info.CRank = Rank; Info.CSigned = std::is_signed<T>::value; - Info.CInputFormat = InputFormat; Info.COutputFormat = OutputFormat; Types.emplace(Info.Name, std::move(Info)); }; - Integral(char{}, "char", 2, " %c", "%c"); - Integral(static_cast<signed char>(0), "signed char", 2, "%hhd", "%hhd"); - Integral(static_cast<unsigned char>(0), "unsigned char", 2, "%hhu", "%hhu"); - Integral(short{}, "short", 3, "%hd", "%hd"); - Integral(static_cast<unsigned short>(0), "unsigned short", 3, "%hu", "%hu"); - Integral(int{}, "int", 4, "%d", "%d"); - Integral(static_cast<unsigned int>(0), "unsigned int", 4, "%u", "%u"); - Integral(long{}, "long", 5, "%ld", "%ld"); - Integral(static_cast<unsigned long>(0), "unsigned long", 5, "%lu", "%lu"); - Integral(static_cast<long long>(0), "long long", 6, "%lld", "%lld"); - Integral(static_cast<unsigned long long>(0), "unsigned long long", 6, "%llu", "%llu"); + Integral(char{}, "char", 2, "%c"); + Integral(static_cast<signed char>(0), "signed char", 2, "%hhd"); + Integral(static_cast<unsigned char>(0), "unsigned char", 2, "%hhu"); + Integral(short{}, "short", 3, "%hd"); + Integral(static_cast<unsigned short>(0), "unsigned short", 3, "%hu"); + Integral(int{}, "int", 4, "%d"); + Integral(static_cast<unsigned int>(0), "unsigned int", 4, "%u"); + Integral(long{}, "long", 5, "%ld"); + Integral(static_cast<unsigned long>(0), "unsigned long", 5, "%lu"); + Integral(static_cast<long long>(0), "long long", 6, "%lld"); + Integral(static_cast<unsigned long long>(0), "unsigned long long", 6, "%llu"); - auto Floating = [&](auto Sample, const char *Name, unsigned Rank, const char *Format) { + auto Floating = [&](auto Sample, const char *Name, unsigned Rank) { TypeInfo Info; Info.Name = cTypeName(Name); Info.CIdentity = Name; Info.Kind = TypeKind::CScalar; Info.LLVMType = nativeFloatType<decltype(Sample)>(Context); Info.CRank = Rank; - Info.CInputFormat = Format; Info.COutputFormat = Rank == 9 ? "%Lf" : "%f"; Types.emplace(Info.Name, std::move(Info)); }; - Floating(float{}, "float", 7, "%f"); - Floating(double{}, "double", 8, "%lf"); - Floating(static_cast<long double>(0), "long double", 9, "%Lf"); + Floating(float{}, "float", 7); + Floating(double{}, "double", 8); + Floating(static_cast<long double>(0), "long double", 9); TypeInfo Bool; Bool.Name = cTypeName("_Bool"); diff --git a/lib/CodeGen/StatementGen.cc b/lib/CodeGen/StatementGen.cc index 0b14cf2..c161049 100644 --- a/lib/CodeGen/StatementGen.cc +++ b/lib/CodeGen/StatementGen.cc @@ -252,35 +252,15 @@ void CodeGen::emitInputStmt(InputStmtAST *In) { return; } - std::vector<Value*> Args; if (TypeInfo->isC()) { - emitCInput(Target.Addr, TypeInfo); - } else if (TypeInfo->isReal()) { - Args.push_back(ScanfFloatFormatStr); - Args.push_back(Target.Addr); - Builder->CreateCall(ScanfFunc, Args); - } else if (TypeInfo->isBoolean()) { - AllocaInst *TempInt = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(), "tmp_bool_input"); - Args.push_back(ScanfFormatStr); - Args.push_back(TempInt); - Builder->CreateCall(ScanfFunc, Args); - - Value *Val = Builder->CreateLoad(Type::getInt64Ty(*TheContext), TempInt); - Value *BoolVal = Builder->CreateICmpNE(Val, ConstantInt::get(*TheContext, APInt(64, 0)), "bool_cast"); - Builder->CreateStore(BoolVal, Target.Addr); + emitCInput(Target.Addr, TypeInfo, In->getLine()); } else if (TypeInfo->isString()) { emitMethodCall(TypeInfo->Name, Target.Addr, "READ_INPUT", {}, In->getLine()); - } else if (TypeInfo->isChar()) { - Value *CharFmt = Builder->CreateGlobalString(" %c", "fmt_in_char", 0, TheModule.get()); - Args.push_back(CharFmt); - Args.push_back(Target.Addr); - Builder->CreateCall(ScanfFunc, Args); - } else if (TypeInfo->Kind == TypeKind::Integer) { - Args.push_back(ScanfFormatStr); - Args.push_back(Target.Addr); - Builder->CreateCall(ScanfFunc, Args); + } else if (TypeInfo->Kind == TypeKind::Integer || TypeInfo->isReal() || TypeInfo->isBoolean() || + TypeInfo->isChar()) { + emitScalarInput(Target.Addr, TypeInfo, In->getLine()); } else { - // Enum/Record/Pointer and any future kind: scanf'ing raw bytes + // Enum/Record/Pointer and any future kind: reading raw bytes // into typed storage would corrupt it — reject explicitly. reportError("INPUT is not supported for a value of type %s", TypeInfo->Name.c_str()); } |
