diff --git a/compiler/AMBuilder b/compiler/AMBuilder index 8a83a8819..137a495e5 100644 --- a/compiler/AMBuilder +++ b/compiler/AMBuilder @@ -12,6 +12,7 @@ module.sources += [ 'data-queue.cpp', 'errors.cpp', 'expressions.cpp', + 'ir.cpp', 'lexer.cpp', 'main.cpp', 'name-resolution.cpp', diff --git a/compiler/array-helpers.cpp b/compiler/array-helpers.cpp index 122386b0b..95089ed20 100644 --- a/compiler/array-helpers.cpp +++ b/compiler/array-helpers.cpp @@ -822,7 +822,7 @@ bool Semantics::CheckArrayDeclaration(VarDeclBase* decl) { class CompoundEmitter final { public: - CompoundEmitter(Type* type, Expr* init) + CompoundEmitter(QualType type, Expr* init) : type_(type), init_(init), pending_zeroes_(0) @@ -852,11 +852,11 @@ class CompoundEmitter final size_t AddString(StringExpr* expr); void AddInlineArray(LayoutFieldDecl* field, ArrayExpr* expr); void AddInlineEnumStruct(EnumStructDecl* es, ArrayExpr* expr); - void EmitPadding(size_t rank_size, Type* type, size_t emitted, bool ellipses, + void EmitPadding(size_t rank_size, QualType type, size_t emitted, bool ellipses, const ke::Maybe prev1, const ke::Maybe prev2); private: - Type* type_; + QualType type_; Expr* init_; tr::vector iv_; tr::vector data_; @@ -948,7 +948,7 @@ cell CompoundEmitter::Emit(ArrayType* rank, Expr* init) { // This only works because enum structs are flattened and don't support // internal IVs. No plans to change this as it would greatly increase // complexity unless we radically changed arrays. - EmitPadding(rank->size(), rank->inner(), emitted, ellipses, prev1, prev2); + EmitPadding(rank->size(), QualType(rank->inner()), emitted, ellipses, prev1, prev2); return (start * sizeof(cell)) | kDataFlag; } @@ -967,7 +967,7 @@ void CompoundEmitter::AddInlineEnumStruct(EnumStructDecl* es, ArrayExpr* array) assert(field); auto rank_type = field->type()->to(); - EmitPadding(rank_type->size(), rank_type->inner(), emitted, false, {}, {}); + EmitPadding(rank_type->size(), QualType(rank_type->inner()), emitted, false, {}, {}); } else if (ArrayExpr* expr = item->as()) { // Subarrays can only appear in an enum struct. Normal 2D cases // would flow through the check at the start of this function. @@ -994,12 +994,12 @@ void CompoundEmitter::AddInlineArray(LayoutFieldDecl* field, ArrayExpr* array) { } auto rank_size = field->type()->to()->size(); - EmitPadding(rank_size, field->type(), array->exprs().size(), + EmitPadding(rank_size, QualType(field->type()), array->exprs().size(), array->ellipses(), prev1, prev2); } void -CompoundEmitter::EmitPadding(size_t rank_size, Type* type, size_t emitted, bool ellipses, +CompoundEmitter::EmitPadding(size_t rank_size, QualType type, size_t emitted, bool ellipses, const ke::Maybe prev1, const ke::Maybe prev2) { // Pad remainder to zeroes if the array was explicitly sized. @@ -1053,20 +1053,20 @@ CompoundEmitter::add_data(cell value) data_.emplace_back(value); } -void BuildCompoundInitializer(Type* type, Expr* init, ArrayData* array) { +void BuildCompoundInitializer(QualType type, Expr* init, ArrayData* array, + std::optional base_address) +{ CompoundEmitter emitter(type, init); emitter.Emit(); array->iv = std::move(emitter.iv()); array->data = std::move(emitter.data()); array->zeroes = emitter.pending_zeroes(); -} -void BuildCompoundInitializer(VarDeclBase* decl, ArrayData* array, cell base_address) { - BuildCompoundInitializer(decl->type(), decl->init_rhs(), array); - - for (auto& v : array->iv) - v += base_address; + if (base_address) { + for (auto& v : array->iv) + v += *base_address; + } } } // namespace cc diff --git a/compiler/array-helpers.h b/compiler/array-helpers.h index 671b281b1..6cbd09f30 100644 --- a/compiler/array-helpers.h +++ b/compiler/array-helpers.h @@ -38,8 +38,8 @@ bool ResolveArrayType(Semantics* sema, const token_pos_t& pos, typeinfo_t* type, // Perform type and size checks of an array and its initializer if present. bool CheckArrayInitialization(Semantics* sema, const typeinfo_t& type, Expr* init); -void BuildCompoundInitializer(VarDeclBase* decl, ArrayData* array, cell_t base_addr); -void BuildCompoundInitializer(Type* type, Expr* init, ArrayData* array); +void BuildCompoundInitializer(QualType type, Expr* init, ArrayData* array, + std::optional base_address = {}); cell_t CalcArraySize(Type* type); diff --git a/compiler/assembler.cpp b/compiler/assembler.cpp index 507394132..f6a504964 100644 --- a/compiler/assembler.cpp +++ b/compiler/assembler.cpp @@ -65,7 +65,7 @@ struct function_entry { function_entry(const function_entry& other) = delete; function_entry& operator =(const function_entry& other) = delete; - FunctionDecl* decl = nullptr; + ir::Function* fun; std::string name; }; @@ -123,7 +123,7 @@ class RttiBuilder RttiBuilder(CompileContext& cc, CodeGenerator& cg, SmxNameTable* names); void finish(SmxBuilder& builder); - void add_method(FunctionDecl* fun); + void add_method(ir::Function* fun); void add_native(FunctionDecl* sym); private: @@ -360,17 +360,19 @@ RttiBuilder::add_debug_var(SmxRttiTable* table, DebugString& var.type_id = type_id; } -void RttiBuilder::add_method(FunctionDecl* fun) { +void RttiBuilder::add_method(ir::Function* fun) { assert(fun->is_live()); uint32_t index = methods_->count(); smx_rtti_method& method = methods_->add(); - method.name = names_->add(fun->name()); - method.pcode_start = fun->cg()->label.offset(); - method.pcode_end = fun->cg()->pcode_end; - method.signature = encode_signature(fun->canonical()); - - if (!fun->cg()->dbgstrs) + method.name = names_->add(fun->decl()->name()); + method.pcode_start = fun->label().offset(); + method.pcode_end = fun->pcode_end(); + method.signature = encode_signature(fun->decl()->canonical()); + + (void)index; +#if 0 + if (!fun->dbgstrs) return; smx_rtti_debug_method debug; @@ -392,6 +394,7 @@ void RttiBuilder::add_method(FunctionDecl* fun) { // Only add a method table entry if we actually had locals. if (debug.first_local != dbg_locals_->count()) dbg_methods_->add(debug); +#endif } void RttiBuilder::add_native(FunctionDecl* fun) { @@ -721,52 +724,36 @@ Assembler::Assemble(SmxByteBuffer* buffer) std::vector functions; std::unordered_set symbols; - // Sort globals. - std::vector global_symbols; - cc_.globals()->ForEachSymbol([&](Decl* decl) -> void { - global_symbols.push_back(decl); + auto mod = cg_.mod(); - // This is only to assert that we embedded pointers properly in the assembly buffer. - symbols.emplace(decl); + // Sort globals. + std::sort(mod->functions().begin(), mod->functions().end(), + [](const ir::Function* a, const ir::Function* b) { + return a->decl()->name()->str() < b->decl()->name()->str(); }); - for (const auto& decl : cc_.functions()) { - if (symbols.count(decl)) - continue; - if (decl->canonical() != decl) + + for (const auto& fun : mod->functions()) { + auto decl = fun->decl(); + + if (decl->is_native() || !fun->body()) continue; - global_symbols.push_back(decl); - symbols.emplace(decl); - } - std::sort(global_symbols.begin(), global_symbols.end(), - [](const Decl* a, const Decl *b) -> bool { - return a->name()->str() < b->name()->str(); - }); + function_entry entry; + entry.fun = fun; + if (decl->is_public()) { + entry.name = decl->name()->str(); + } else { + // Create a private name. + entry.name = ke::StringPrintf(".%d.%s", fun->label().offset(), decl->name()->chars()); + } + + functions.emplace_back(std::move(entry)); + } +#if 0 // Build the easy symbol tables. for (const auto& decl : global_symbols) { - if (auto fun = decl->as()) { - if (fun->is_native()) - continue; - - if (!fun->body()) - continue; - if (!fun->is_live()) - continue; - if (fun->canonical() != fun) - continue; - - function_entry entry; - entry.decl = fun; - if (fun->is_public()) { - entry.name = fun->name()->str(); - } else { - // Create a private name. - entry.name = ke::StringPrintf(".%d.%s", fun->cg()->label.offset(), fun->name()->chars()); - } - - functions.emplace_back(std::move(entry)); - } else if (auto var = decl->as()) { + if (auto var = decl->as()) { if (var->is_public() || (var->is_used() && !var->as())) { sp_file_pubvars_t& pubvar = pubvars->add(); pubvar.address = var->addr(); @@ -774,6 +761,7 @@ Assembler::Assemble(SmxByteBuffer* buffer) } } } +#endif // The public list must be sorted. std::sort(functions.begin(), functions.end(), @@ -783,31 +771,27 @@ Assembler::Assemble(SmxByteBuffer* buffer) for (size_t i = 0; i < functions.size(); i++) { function_entry& f = functions[i]; - assert(f.decl->cg()->label.offset() > 0); - assert(f.decl->impl()); - assert(f.decl->cg()->pcode_end > f.decl->cg()->label.offset()); - sp_file_publics_t& pubfunc = publics->add(); - pubfunc.address = f.decl->cg()->label.offset(); + pubfunc.address = f.fun->label().offset(); pubfunc.name = names->add(*cc_.atoms(), f.name.c_str()); auto id = (uint32_t(i) << 1) | 1; if (!Label::ValueFits(id)) report(421); - cg_.LinkPublicFunction(f.decl, id); + cg_.LinkPublicFunction(f.fun, id); - rtti.add_method(f.decl); + rtti.add_method(f.fun); } // Populate the native table. for (size_t i = 0; i < cg_.native_list().size(); i++) { - FunctionDecl* sym = cg_.native_list()[i]; - assert(size_t(sym->cg()->label.offset()) == i); + ir::Function* sym = cg_.native_list()[i]; + assert(size_t(sym->label().offset()) == i); sp_file_natives_t& entry = natives->add(); - entry.name = names->add(sym->name()); + entry.name = names->add(sym->decl()->name()); - rtti.add_native(sym); + rtti.add_native(sym->decl()); } // Set up the code section. diff --git a/compiler/assembler.h b/compiler/assembler.h index 2226f17b4..2d32db867 100644 --- a/compiler/assembler.h +++ b/compiler/assembler.h @@ -26,6 +26,7 @@ #include "libsmx/data-pool.h" #include "libsmx/smx-builder.h" #include "libsmx/smx-encoding.h" +#include "ir.h" #include "sc.h" #include "shared/byte-buffer.h" #include "shared/string-pool.h" diff --git a/compiler/ast-types.h b/compiler/ast-types.h index bc9d81701..657a09b0f 100644 --- a/compiler/ast-types.h +++ b/compiler/ast-types.h @@ -19,6 +19,8 @@ // 3. This notice may not be removed or altered from any source distribution. #pragma once +#include + #define AST_STMT_TYPE_LIST(FOR_EACH) \ FOR_EACH(StmtList) \ FOR_EACH(BlockStmt) \ @@ -79,6 +81,48 @@ FOR_EACH(StructExpr) \ FOR_EACH(StructInitFieldExpr) +#define IR_NODE_TYPE_LIST(FOR_EACH) \ + /* Decls */ \ + FOR_EACH(Function) \ + FOR_EACH(Variable) \ + /* Statements */ \ + FOR_EACH(Return) \ + FOR_EACH(ValueInsn) \ + FOR_EACH(Exit) \ + FOR_EACH(Break) \ + FOR_EACH(Continue) \ + FOR_EACH(Assert) \ + FOR_EACH(If) \ + FOR_EACH(DoWhile) \ + FOR_EACH(Delete) \ + FOR_EACH(ForLoop) \ + FOR_EACH(Switch) \ + FOR_EACH(FunctionDef) \ + /* Values */ \ + FOR_EACH(ConstVal) \ + FOR_EACH(CharArrayLiteral) \ + FOR_EACH(VariableRef) \ + FOR_EACH(TypeRef) \ + FOR_EACH(FunctionRef) \ + FOR_EACH(IndexOp) \ + FOR_EACH(Load) \ + FOR_EACH(TernaryOp) \ + FOR_EACH(BinaryOp) \ + FOR_EACH(Array) \ + FOR_EACH(CommaOp) \ + FOR_EACH(CallOp) \ + FOR_EACH(TempRef) \ + FOR_EACH(PropertyRef) \ + FOR_EACH(FieldRef) \ + FOR_EACH(UnaryOp) \ + FOR_EACH(CallUserOp) \ + FOR_EACH(IncDecOp) \ + FOR_EACH(Store) \ + FOR_EACH(ThisRef) + +namespace sp { +namespace cc { + enum class ExprKind : uint8_t { #define _(Name) Name, @@ -92,3 +136,13 @@ enum class StmtKind : uint8_t AST_STMT_TYPE_LIST(_) #undef _ }; + +enum class IrKind : uint8_t +{ +#define _(Name) Name, + IR_NODE_TYPE_LIST(_) +#undef _ +}; + +} // namespace cc +} // namespace sp diff --git a/compiler/code-generator.cpp b/compiler/code-generator.cpp index 5f08dd686..081528a79 100644 --- a/compiler/code-generator.cpp +++ b/compiler/code-generator.cpp @@ -41,9 +41,9 @@ namespace cc { #define __ asm_. -CodeGenerator::CodeGenerator(CompileContext& cc, ParseTree* tree) +CodeGenerator::CodeGenerator(CompileContext& cc, std::shared_ptr mod) : cc_(cc), - tree_(tree) + mod_(mod) { } @@ -51,7 +51,9 @@ bool CodeGenerator::Generate() { // First instruction is always halt. __ emit(OP_HALT, 0); - EmitStmtList(tree_->stmts()); + for (const auto& fun : mod_->functions()) + EmitFunction(fun); + if (!ComputeStackUsage()) return false; @@ -76,6 +78,7 @@ CodeGenerator::AddDebugFile(const std::string& file) void CodeGenerator::AddDebugLine(int linenr) { +#if 0 auto str = ke::StringPrintf("L:%x %x", asm_.position(), linenr); if (fun_) { auto data = fun_->cg(); @@ -85,6 +88,7 @@ CodeGenerator::AddDebugLine(int linenr) } else { debug_strings_.emplace_back(str.c_str(), str.size()); } +#endif } void CodeGenerator::AddDebugSymbol(Decl* decl, uint32_t pc) { @@ -106,10 +110,12 @@ void CodeGenerator::AddDebugSymbol(Decl* decl, uint32_t pc) { asm_.position(), decl->ident(), decl->vclass(), (int)decl->is_const()); if (fun_) { +#if 0 auto data = fun_->cg(); if (!data->dbgstrs) data->dbgstrs = cc_.NewDebugStringList(); data->dbgstrs->emplace_back(string.c_str(), string.size()); +#endif } else { debug_strings_.emplace_back(string.c_str(), string.size()); } @@ -122,6 +128,49 @@ void CodeGenerator::AddDebugSymbols(tr::vector* list) { } } +void CodeGenerator::EmitBlock(ir::StreamNode* list) { + for (auto iter = list; iter; iter = iter->next()) + EmitInsn(iter); +} + +void CodeGenerator::EmitInsn(ir::StreamNode* node) { + switch (node->kind()) { + case IrKind::Return: + EmitReturn(node->as()); + break; + case IrKind::Variable: + EmitVarDecl(node->as()); + break; + case IrKind::ValueInsn: + EmitValueInsn(node->as()); + break; + case IrKind::DoWhile: + EmitDoWhile(node->as()); + break; + case IrKind::If: + EmitIf(node->as()); + break; + case IrKind::Break: + EmitLoopControl(tBREAK); + break; + case IrKind::Continue: + EmitLoopControl(tCONTINUE); + break; + default: + assert(false); + } +} + +void CodeGenerator::EmitValueInsn(ir::ValueInsn* insn) { + EmitValue(insn->val()); +} + +void CodeGenerator::EmitCommaOp(ir::CommaOp* op) { + for (const auto& val : op->values()) + EmitValue(val); +} + +#if 0 void CodeGenerator::EmitStmtList(StmtList* list) { @@ -238,6 +287,7 @@ CodeGenerator::EmitStmt(Stmt* stmt) if (stmt->tree_has_heap_allocs()) LeaveHeapScope(); } +#endif void CodeGenerator::EmitChangeScopeNode(ChangeScopeNode* node) @@ -269,22 +319,30 @@ CodeGenerator::EmitChangeScopeNode(ChangeScopeNode* node) } } -void CodeGenerator::EmitVarDecl(VarDeclBase* decl) { +void CodeGenerator::EmitVarDecl(ir::Variable* var) { +#if 0 if (decl->type()->isPstruct()) { EmitPstruct(decl); - } else { - if (decl->ident() != iCONSTEXPR) { - if (decl->vclass() == sLOCAL) - EmitLocalVar(decl); - else - EmitGlobalVar(decl); - } + } else +#endif + { + if (var->decl()->vclass() == sLOCAL) + EmitLocalVar(var); + else + assert(false); +#if 0 + else + EmitGlobalVar(decl); +#endif } +#if 0 if (decl->is_public() || decl->is_used()) EnqueueDebugSymbol(decl, asm_.position()); +#endif } +#if 0 void CodeGenerator::EmitGlobalVar(VarDeclBase* decl) { BinaryExpr* init = decl->init(); @@ -312,29 +370,22 @@ void CodeGenerator::EmitGlobalVar(VarDeclBase* decl) { assert(false); } } +#endif -void CodeGenerator::EmitLocalVar(VarDeclBase* decl) { - BinaryExpr* init = decl->init(); - - bool is_struct = decl->type()->isEnumStruct(); - bool is_array = decl->type()->isArray(); +void CodeGenerator::EmitLocalVar(ir::Variable* var) { + auto init = var->init(); + bool is_struct = var->decl()->type()->isEnumStruct(); + bool is_array = var->decl()->type()->isArray(); if (!is_array && !is_struct) { - markstack(decl, MEMUSE_STATIC, 1); - decl->BindAddress(-current_stack_ * sizeof(cell)); + cell_t addr = markstack(var->pn(), MEMUSE_STATIC, 1); + var->set_addr(addr); if (init) { - const auto& val = init->right()->val(); - if (init->assignop().sym) { - EmitExpr(init->right()); - - value tmp = val; - EmitUserOp(init->assignop(), &tmp); - __ emit(OP_PUSH_PRI); - } else if (val.ident == iCONSTEXPR) { - __ emit(OP_PUSH_C, val.constval()); + if (auto cv = init->as()) { + __ emit(OP_PUSH_C, cv->value()); } else { - EmitExpr(init->right()); + EmitValue(init); __ emit(OP_PUSH_PRI); } } else { @@ -344,22 +395,28 @@ void CodeGenerator::EmitLocalVar(VarDeclBase* decl) { } else { // Note that genarray() pushes the address onto the stack, so we don't // need to call modstk() here. - TrackHeapAlloc(decl, MEMUSE_DYNAMIC, 0); - markstack(decl, MEMUSE_STATIC, 1); - decl->BindAddress(-current_stack_ * sizeof(cell)); + TrackHeapAlloc(var->decl(), MEMUSE_DYNAMIC, 0); + cell_t addr = markstack(var->decl(), MEMUSE_STATIC, 1); + var->set_addr(addr); +#if 0 auto init_rhs = decl->init_rhs(); +#endif + Expr* init_rhs = nullptr; if (init_rhs && init_rhs->as()) { + assert(false); +#if 0 EmitExpr(init_rhs->as()); - } else if (!init_rhs || decl->type()->isArray() || is_struct) { +#endif + } else if (!init_rhs || is_array || is_struct) { ArrayData array; - BuildCompoundInitializer(decl, &array, 0); + BuildCompoundInitializer(QualType(var->decl()->type()), init_rhs, &array); cell iv_size = (cell)array.iv.size(); cell data_size = (cell)array.data.size() + array.zeroes; cell total_size = iv_size + data_size; - TrackHeapAlloc(decl, MEMUSE_STATIC, total_size); + TrackHeapAlloc(var->decl(), MEMUSE_STATIC, total_size); cell iv_addr = data_.dat_address(); data_.Add(std::move(array.iv)); @@ -378,7 +435,7 @@ void CodeGenerator::EmitLocalVar(VarDeclBase* decl) { // optimization for older plugins so we don't introduce any // surprises. Note we zap the fill size *after* computing the // non-fill size, since we need to compute the copy size correctly. - if (!decl->autozero() && array.zeroes) + if (!var->decl()->autozero() && array.zeroes) array.zeroes = 0; __ emit(OP_HEAP, total_size * sizeof(cell)); @@ -393,16 +450,17 @@ void CodeGenerator::EmitLocalVar(VarDeclBase* decl) { assert(cells > 0); __ emit(OP_PUSH_C, cells); - if (decl->autozero()) + if (var->decl()->autozero()) __ emit(OP_GENARRAY_Z, 1); else __ emit(OP_GENARRAY, 1); __ const_pri(str_addr); - __ copyarray(decl, cells * sizeof(cell)); + __ copyarray(var, cells * sizeof(cell)); } } } +#if 0 void CodeGenerator::EmitPstruct(VarDeclBase* decl) { @@ -437,21 +495,83 @@ CodeGenerator::EmitPstruct(VarDeclBase* decl) for (const auto& value : values) data_.Add(value); } +#endif -void -CodeGenerator::EmitExpr(Expr* expr) -{ - AutoErrorPos aep(expr->pos()); +void CodeGenerator::EmitValue(ir::Value* val) { + AutoErrorPos aep(val->pn()->pos()); - if (expr->val().ident == iCONSTEXPR) { - __ const_pri(expr->val().constval()); - return; + switch (val->kind()) { + case IrKind::ConstVal: + EmitConst(val->as()); + break; + case IrKind::UnaryOp: + EmitUnary(val->as()); + break; + case IrKind::Load: + EmitLoad(val->as()); + break; + case IrKind::CommaOp: + EmitCommaOp(val->as()); + break; + case IrKind::CallOp: + EmitCallOp(val->as()); + break; + case IrKind::BinaryOp: + EmitBinary(val->as()); + break; + case IrKind::CharArrayLiteral: + EmitCharArrayLiteral(val->as()); + break; + case IrKind::TernaryOp: + EmitTernaryOp(val->as()); + break; + default: + assert(false); + break; } +} - switch (expr->kind()) { - case ExprKind::UnaryExpr: - EmitUnary(expr->to()); +void CodeGenerator::EmitLoad(ir::Load* load) { + auto lval = load->lval(); + switch (lval->kind()) { + case IrKind::VariableRef: { + auto var = lval->as()->var(); + auto vclass = var->decl()->vclass(); + if (vclass == sLOCAL || vclass == sARGUMENT) { + if (lval->type()->isReference()) + __ emit(OP_LREF_S_PRI, var->addr()); + else + __ emit(OP_LOAD_S_PRI, var->addr()); + } else { + __ emit(OP_LOAD_PRI, var->addr()); + } break; + } + default: + assert(false); + } +} + +void CodeGenerator::EmitCallOp(ir::CallOp* call) { + auto target = call->target(); + assert(!target->decl()->return_array()); + + const auto& argv = call->argv(); + for (size_t i = argv.size() - 1; i < argv.size(); i--) { + EmitValue(argv[i]); + __ emit(OP_PUSH_PRI); + } + + EmitCall(target, (cell_t)argv.size()); + + assert(!target->decl()->return_array()); +} + +#if 0 +void +CodeGenerator::EmitExpr(Expr* expr) +{ + switch (expr->kind()) { case ExprKind::IncDecExpr: EmitIncDec(expr->to()); break; @@ -526,40 +646,48 @@ CodeGenerator::EmitExpr(Expr* expr) EmitNewArrayExpr(expr->to()); break; case ExprKind::NamedArgExpr: - EmitExpr(expr->to()->expr); + EmitExpr(expr->to()->expr()); break; default: assert(false); } } +#endif -void -CodeGenerator::EmitTest(Expr* expr, bool jump_on_true, Label* target) -{ - switch (expr->kind()) { - case ExprKind::LogicalExpr: - EmitLogicalExprTest(expr->to(), jump_on_true, target); - return; - case ExprKind::UnaryExpr: - if (EmitUnaryExprTest(expr->to(), jump_on_true, target)) +void CodeGenerator::EmitConst(ir::Const* cv) { + __ const_pri(cv->value()); +} + +static inline ir::BinaryOp* ToLogical(ir::Value* op) { + if (auto bin = op->as()) { + if (bin->token() == tlAND || bin->token() == tlOR) + return bin; + } + return nullptr; +} + +void CodeGenerator::EmitTest(ir::Value* test, bool jump_on_true, sp::Label* target) { + switch (test->kind()) { + case IrKind::UnaryOp: + if (EmitUnaryExprTest(test->to(), jump_on_true, target)) return; break; + case IrKind::BinaryOp: + if (auto bin = ToLogical(test)) { + EmitLogicalExprTest(bin->to(), jump_on_true, target); + return; + } + break; +#if 0 case ExprKind::ChainedCompareExpr: if (EmitChainedCompareExprTest(expr->to(), jump_on_true, target)) return; break; - case ExprKind::CommaExpr: { - auto ce = expr->to(); - for (size_t i = 0; i < ce->exprs().size() - 1; i++) - EmitExpr(ce->exprs().at(i)); - - EmitTest(ce->exprs().back(), jump_on_true, target); - return; - } +#endif } - EmitExpr(expr); + EmitValue(test); if (jump_on_true) __ emit(OP_JNZ, target); @@ -567,17 +695,10 @@ CodeGenerator::EmitTest(Expr* expr, bool jump_on_true, Label* target) __ emit(OP_JZER, target); } -void -CodeGenerator::EmitUnary(UnaryExpr* expr) -{ - EmitExpr(expr->expr()); - - // Hack: abort early if the operation was already handled. We really just - // want to replace the UnaryExpr though. - if (expr->userop()) - return; +void CodeGenerator::EmitUnary(ir::UnaryOp* op) { + EmitValue(op->val()); - switch (expr->token()) { + switch (op->expr()->token()) { case '~': __ emit(OP_INVERT); break; @@ -592,50 +713,147 @@ CodeGenerator::EmitUnary(UnaryExpr* expr) } } -bool -CodeGenerator::EmitUnaryExprTest(UnaryExpr* expr, bool jump_on_true, Label* target) -{ - if (!expr->userop() && expr->token() == '!') { - EmitTest(expr->expr(), !jump_on_true, target); +bool CodeGenerator::EmitUnaryExprTest(ir::UnaryOp* op, bool jump_on_true, Label* target) { + if (op->expr()->token() == '!') { + EmitTest(op->val(), !jump_on_true, target); return true; } return false; } -void -CodeGenerator::EmitIncDec(IncDecExpr* expr) -{ - EmitExpr(expr->expr()); +void CodeGenerator::EmitBinary(ir::BinaryOp* op) { + if (op->token() == tlOR || op->token() == tlAND) { + bool jump_on_true = (op->token() == tlOR); - const auto& val = expr->expr()->val(); - auto& userop = expr->userop(); - value tmp = val; + Label shortcircuit, done; - if (expr->prefix()) { - if (val.ident != iACCESSOR) { - if (userop.sym) { - EmitUserOp(userop, &tmp); + EmitTest(op, jump_on_true, &shortcircuit); + __ const_pri(!jump_on_true); + __ emit(OP_JUMP, &done); + __ bind(&shortcircuit); + __ const_pri(jump_on_true); + __ bind(&done); + return; + } + + if (auto left_cv = op->left()->as()) { + if (auto right_cv = op->right()->as()) + __ const_pri(right_cv->value()); + else + EmitValue(op->right()); + __ const_alt(left_cv->value()); + } else { + EmitValue(op->left()); + + if (auto right_cv = op->right()->as()) { + if (IsOperTokenCommutative(op->token())) { + __ const_alt(right_cv->value()); } else { - if (expr->token() == tINC) - EmitInc(&tmp); /* increase variable first */ - else - EmitDec(&tmp); + __ emit(OP_PUSH_PRI); + __ const_pri(right_cv->value()); + __ emit(OP_POP_ALT); } - EmitRvalue(&tmp); /* and read the result into PRI */ } else { __ emit(OP_PUSH_PRI); - InvokeGetter(val.accessor()); - if (userop.sym) { - EmitUserOp(userop, &tmp); - } else { - if (expr->token() == tINC) - __ emit(OP_INC_PRI); - else - __ emit(OP_DEC_PRI); - } + EmitValue(op->right()); __ emit(OP_POP_ALT); - InvokeSetter(val.accessor(), TRUE); } + } + + switch (op->token()) { + case '*': + __ emit(OP_SMUL); + break; + case '/': + __ emit(OP_SDIV_ALT); + break; + case '%': + __ emit(OP_SDIV_ALT); + __ emit(OP_MOVE_PRI); + break; + case '+': + __ emit(OP_ADD); + break; + case '-': + __ emit(OP_SUB_ALT); + break; + case tSHL: + __ emit(OP_XCHG); + __ emit(OP_SHL); + break; + case tSHR: + __ emit(OP_XCHG); + __ emit(OP_SSHR); + break; + case tSHRU: + __ emit(OP_XCHG); + __ emit(OP_SHR); + break; + case '&': + __ emit(OP_AND); + break; + case '^': + __ emit(OP_XOR); + break; + case '|': + __ emit(OP_OR); + break; + case tlLE: + __ emit(OP_XCHG); + __ emit(OP_SLEQ); + break; + case tlGE: + __ emit(OP_XCHG); + __ emit(OP_SGEQ); + break; + case '<': + __ emit(OP_XCHG); + __ emit(OP_SLESS); + break; + case '>': + __ emit(OP_XCHG); + __ emit(OP_SGRTR); + break; + case tlEQ: + __ emit(OP_EQ); + break; + case tlNE: + __ emit(OP_NEQ); + break; + + default: + assert(false); + } +} + +void CodeGenerator::EmitTernaryOp(ir::TernaryOp* op) { + Label on_false, done; + EmitTest(op->select(), false, &on_false); + EmitValue(op->on_true()); + __ emit(OP_JUMP, &done); + __ bind(&on_false); + EmitValue(op->on_false()); + __ bind(&done); +} + +void CodeGenerator::EmitCharArrayLiteral(ir::CharArrayLiteral* val) { + auto text = val->expr()->text(); + auto addr = data_.dat_address(); + data_.Add(text->chars(), text->length()); + __ const_pri(addr); +} + +void CodeGenerator::EmitIncDec(ir::IncDecOp* op) { +#if 0 + auto expr = op->expr(); + bool saved_lval = EmitPrecalcLvalue(op->lval()); + + assert(!op->getter()); + assert(!op->lval()->kind() == IrKind::PropertyRef); + + if (expr->prefix()) { + EmitIncDecInner(op->lval()); + EmitRvalue(&tmp); /* and read the result into PRI */ } else { if (val.ident == iARRAYCELL || val.ident == iARRAYCHAR || val.ident == iACCESSOR) { // Save base address. Stack: [addr] @@ -679,205 +897,26 @@ CodeGenerator::EmitIncDec(IncDecExpr* expr) __ emit(OP_POP_PRI); } } +#endif } -void -CodeGenerator::EmitBinary(BinaryExpr* expr) -{ - auto token = expr->token(); - auto left = expr->left(); - auto right = expr->right(); - auto oper = expr->oper(); - - assert(!IsChainedOp(token)); - - // We emit constexprs in the |oper_| handler below. - const auto& left_val = left->val(); - if (IsAssignOp(token) || left_val.ident != iCONSTEXPR) - EmitExpr(left); - - bool saved_lhs = false; - if (IsAssignOp(token)) { - if (left_val.ident == iARRAYCELL || left_val.ident == iARRAYCHAR || - left_val.type()->isArray()) - { - if (oper) { - __ emit(OP_PUSH_PRI); - EmitRvalue(left_val); - saved_lhs = true; - } - } else if (left_val.ident == iACCESSOR) { - __ emit(OP_PUSH_PRI); - if (oper) - EmitRvalue(left_val); - saved_lhs = true; - } else { - assert(left->lvalue()); - if (oper) - EmitRvalue(left_val); - } - - if (expr->array_copy_length()) { - assert(!oper); - assert(!expr->assignop().sym); - - __ emit(OP_PUSH_PRI); - EmitExpr(right); - __ emit(OP_POP_ALT); - __ emit(OP_MOVS, expr->array_copy_length() * sizeof(cell)); +static void FlattenLogical(ir::Value* op, int token, std::vector* out) { + if (auto bin = ToLogical(op)) { + if (bin->token() == token) { + out->emplace_back(bin->left()); + out->emplace_back(bin->right()); return; } } - - assert(!expr->array_copy_length()); - assert(!left_val.type()->isArray()); - - EmitBinaryInner(oper, expr->userop(), left, right); - - if (IsAssignOp(token)) { - if (saved_lhs) - __ emit(OP_POP_ALT); - - auto tmp = left_val; - if (expr->assignop().sym) - EmitUserOp(expr->assignop(), nullptr); - EmitStore(&tmp); - } + out->emplace_back(op); } -void -CodeGenerator::EmitBinaryInner(int oper_tok, const UserOperation& in_user_op, Expr* left, - Expr* right) -{ - const auto& left_val = left->val(); - const auto& right_val = right->val(); +void CodeGenerator::EmitLogicalExprTest(ir::BinaryOp* root, bool jump_on_true, sp::Label* target) { + assert(root->token() == tlAND || root->token() == tlOR); - UserOperation user_op = in_user_op; - - // left goes into ALT, right goes into PRI, though we can swap them for - // commutative operations. - if (left_val.ident == iCONSTEXPR) { - if (right_val.ident == iCONSTEXPR) - __ const_pri(right_val.constval()); - else - EmitExpr(right); - __ const_alt(left_val.constval()); - } else { - // If performing a binary operation, we need to make sure the LHS winds - // up in ALT. If performing a store, we only need to preserve LHS to - // ALT if it can't be re-evaluated. - bool must_save_lhs = oper_tok || !left_val.canRematerialize(); - if (right_val.ident == iCONSTEXPR) { - if (commutative(oper_tok)) { - __ const_alt(right_val.constval()); - user_op.swapparams ^= true; - } else { - if (must_save_lhs) - __ emit(OP_PUSH_PRI); - __ const_pri(right_val.constval()); - if (must_save_lhs) - __ emit(OP_POP_ALT); - } - } else { - if (must_save_lhs) - __ emit(OP_PUSH_PRI); - EmitExpr(right); - if (must_save_lhs) - __ emit(OP_POP_ALT); - } - } - - if (oper_tok) { - if (user_op.sym) { - EmitUserOp(user_op, nullptr); - return; - } - switch (oper_tok) { - case '*': - __ emit(OP_SMUL); - break; - case '/': - __ emit(OP_SDIV_ALT); - break; - case '%': - __ emit(OP_SDIV_ALT); - __ emit(OP_MOVE_PRI); - break; - case '+': - __ emit(OP_ADD); - break; - case '-': - __ emit(OP_SUB_ALT); - break; - case tSHL: - __ emit(OP_XCHG); - __ emit(OP_SHL); - break; - case tSHR: - __ emit(OP_XCHG); - __ emit(OP_SSHR); - break; - case tSHRU: - __ emit(OP_XCHG); - __ emit(OP_SHR); - break; - case '&': - __ emit(OP_AND); - break; - case '^': - __ emit(OP_XOR); - break; - case '|': - __ emit(OP_OR); - break; - case tlLE: - __ emit(OP_XCHG); - __ emit(OP_SLEQ); - break; - case tlGE: - __ emit(OP_XCHG); - __ emit(OP_SGEQ); - break; - case '<': - __ emit(OP_XCHG); - __ emit(OP_SLESS); - break; - case '>': - __ emit(OP_XCHG); - __ emit(OP_SGRTR); - break; - case tlEQ: - __ emit(OP_EQ); - break; - case tlNE: - __ emit(OP_NEQ); - break; - default: - assert(false); - } - } -} - -void -CodeGenerator::EmitLogicalExpr(LogicalExpr* expr) -{ - bool jump_on_true = expr->token() == tlOR; - - Label shortcircuit, done; - - EmitTest(expr, jump_on_true, &shortcircuit); - __ const_pri(!jump_on_true); - __ emit(OP_JUMP, &done); - __ bind(&shortcircuit); - __ const_pri(jump_on_true); - __ bind(&done); -} - -void -CodeGenerator::EmitLogicalExprTest(LogicalExpr* root, bool jump_on_true, Label* target) -{ - std::vector sequence; - root->FlattenLogical(root->token(), &sequence); + std::vector sequence; + FlattenLogical(root->left(), root->token(), &sequence); + FlattenLogical(root->right(), root->token(), &sequence); // a || b || c .... given jumpOnTrue, should be: // @@ -923,26 +962,26 @@ CodeGenerator::EmitLogicalExprTest(LogicalExpr* root, bool jump_on_true, Label* Label fallthrough; for (size_t i = 0; i < sequence.size() - 1; i++) { - auto expr = sequence.at(i); + auto op = sequence.at(i); if (root->token() == tlOR) { if (jump_on_true) - EmitTest(expr, true, target); + EmitTest(op, true, target); else - EmitTest(expr, true, &fallthrough); + EmitTest(op, true, &fallthrough); } else { assert(root->token() == tlAND); if (jump_on_true) - EmitTest(expr, false, &fallthrough); + EmitTest(op, false, &fallthrough); else - EmitTest(expr, false, target); + EmitTest(op, false, target); } } - Expr* last = sequence.back(); - EmitTest(last, jump_on_true, target); + EmitTest(sequence.back(), jump_on_true, target); __ bind(&fallthrough); } +#if 0 static inline OPCODE CmpTokenToOp(int token) { @@ -960,7 +999,9 @@ CmpTokenToOp(int token) return OP_HALT; } } +#endif +#if 0 bool CodeGenerator::EmitChainedCompareExprTest(ChainedCompareExpr* root, bool jump_on_true, Label* target) @@ -1024,21 +1065,6 @@ CodeGenerator::EmitChainedCompareExpr(ChainedCompareExpr* root) } } -void -CodeGenerator::EmitTernaryExpr(TernaryExpr* expr) -{ - EmitExpr(expr->first()); - - Label flab1, flab2; - - __ emit(OP_JZER, &flab1); - EmitExpr(expr->second()); - __ emit(OP_JUMP, &flab2); - __ bind(&flab1); - EmitExpr(expr->third()); - __ bind(&flab2); -} - void CodeGenerator::EmitSymbolExpr(SymbolExpr* expr) { @@ -1062,17 +1088,14 @@ CodeGenerator::EmitSymbolExpr(SymbolExpr* expr) assert(false); } } +#endif -void -CodeGenerator::EmitIndexExpr(IndexExpr* expr) -{ - EmitExpr(expr->base()); - - auto& base_val = expr->base()->val(); +void CodeGenerator::EmitIndexOp(ir::IndexOp* op) { + EmitValue(op->base()); cell_t rank_size = sizeof(cell_t); - auto array_type = base_val.type()->as(); + auto array_type = op->base()->type()->as(); if (!array_type->inner()->isArray()) { if (array_type->inner()->isChar()) rank_size = 1; @@ -1082,13 +1105,12 @@ CodeGenerator::EmitIndexExpr(IndexExpr* expr) assert(rank_size == 1 || (rank_size % sizeof(cell_t) == 0)); - const auto& idxval = expr->index()->val(); - if (idxval.ident == iCONSTEXPR) { - if (idxval.constval() != 0) - __ emit(OP_ADD_C, idxval.constval() * rank_size); + if (auto cv = op->index()->as()) { + if (cv->value() != 0) + __ emit(OP_ADD_C, cv->value() * rank_size); } else { __ emit(OP_PUSH_PRI); - EmitExpr(expr->index()); + EmitValue(op->index()); if (array_type->size()) { __ emit(OP_BOUNDS, array_type->size() - 1); /* run time check for array bounds */ @@ -1110,33 +1132,21 @@ CodeGenerator::EmitIndexExpr(IndexExpr* expr) // The indexed item is another array (multi-dimensional arrays). if (array_type->inner()->isArray()) { - assert(expr->val().type()->isArray()); + assert(op->type()->isArray()); __ emit(OP_LOAD_I); } } -void -CodeGenerator::EmitFieldAccessExpr(FieldAccessExpr* expr) -{ - assert(expr->token() == '.'); - - // Note that we do not load an iACCESSOR here, we only make sure the base - // is computed. Emit() never performs loads on l-values, that ability is - // reserved for RvalueExpr(). - EmitExpr(expr->base()); - - // Only enum struct accesses have a resolved decl. - if (!expr->resolved()) - return; +void CodeGenerator::EmitFieldRef(ir::FieldRef* ref) { + EmitValue(ref->base()); - if (LayoutFieldDecl* field = expr->resolved()->as()) { - if (field->offset()) { - __ const_alt(field->offset() << 2); - __ emit(OP_ADD); - } + if (ref->field()->offset()) { + __ const_alt(ref->field()->offset() << 2); + __ emit(OP_ADD); } } +#if 0 void CodeGenerator::EmitCallExpr(CallExpr* call) { // If returning an array, push a hidden parameter. if (call->fun()->return_array()) { @@ -1278,31 +1288,29 @@ void CodeGenerator::EmitNewArrayExpr(NewArrayExpr* expr) { else __ emit(OP_GENARRAY, numdim); } +#endif -void -CodeGenerator::EmitIfStmt(IfStmt* stmt) -{ +void CodeGenerator::EmitIf(ir::If* insn) { Label flab1; - EmitTest(stmt->cond(), false, &flab1); - EmitStmt(stmt->on_true()); - if (stmt->on_false()) { + EmitTest(insn->cond(), false, &flab1); + EmitBlock(insn->on_true()); + if (insn->on_false()) { Label flab2; - if (!stmt->on_true()->IsTerminal()) { - __ emit(OP_JUMP, &flab2); - } + __ emit(OP_JUMP, &flab2); __ bind(&flab1); - EmitStmt(stmt->on_false()); - if (flab2.used()) - __ bind(&flab2); + EmitBlock(insn->on_false()); + __ bind(&flab2); } else { __ bind(&flab1); } } -void CodeGenerator::EmitReturnArrayStmt(ReturnStmt* stmt) { +void CodeGenerator::EmitReturnArray(ir::Return* node) { + assert(false); +#if 0 ArrayData array; - BuildCompoundInitializer(stmt->expr()->val().type(), nullptr, &array); + BuildCompoundInitializer(node->val()->type(), nullptr, &array); auto info = fun_->return_array(); if (array.iv.empty()) { @@ -1348,17 +1356,15 @@ void CodeGenerator::EmitReturnArrayStmt(ReturnStmt* stmt) { __ emit(OP_POP_PRI); __ emit(OP_ADD_C, iv_size * sizeof(cell)); __ emit(OP_MOVS, info->zeroes * sizeof(cell)); +#endif } -void -CodeGenerator::EmitReturnStmt(ReturnStmt* stmt) -{ - if (stmt->expr()) { - EmitExpr(stmt->expr()); +void CodeGenerator::EmitReturn(ir::Return* node) { + if (node->val()) { + EmitValue(node->val()); - const auto& v = stmt->expr()->val(); - if (v.type()->isArray()) - EmitReturnArrayStmt(stmt); + if (node->val()->type()->isArray()) + EmitReturnArray(node); } else { /* this return statement contains no expression */ __ const_pri(0); @@ -1368,6 +1374,7 @@ CodeGenerator::EmitReturnStmt(ReturnStmt* stmt) __ emit(OP_RETN); } +#if 0 void CodeGenerator::EmitDeleteStmt(DeleteStmt* stmt) { @@ -1513,10 +1520,10 @@ void CodeGenerator::InvokeSetter(MethodmapPropertyDecl* prop, bool save_pri) { if (save_pri) __ emit(OP_POP_PRI); } +#endif -void -CodeGenerator::EmitDoWhileStmt(DoWhileStmt* stmt) -{ +void CodeGenerator::EmitDoWhile(ir::DoWhile* loop) { + auto stmt = loop->stmt(); int token = stmt->token(); assert(token == tDO || token == tWHILE); @@ -1525,37 +1532,35 @@ CodeGenerator::EmitDoWhileStmt(DoWhileStmt* stmt) loop_cx.heap_scope_id = heap_scope_id(); ke::SaveAndSet push_context(&loop_, &loop_cx); - auto body = stmt->body(); - auto cond = stmt->cond(); + auto body = loop->body(); + auto cond = loop->cond(); if (token == tDO) { Label start; __ bind(&start); - EmitStmt(body); + EmitBlock(body); __ bind(&loop_cx.continue_to); - if (body->flow_type() != Flow_Break && body->flow_type() != Flow_Return) { - if (cond->tree_has_heap_allocs()) { - // Need to create a temporary heap scope here. - Label on_true, join; - EnterHeapScope(Flow_None); - EmitTest(cond, true, &on_true); - __ emit(OP_PUSH_C, 0); - __ emit(OP_JUMP, &join); - __ bind(&on_true); - __ emit(OP_PUSH_C, 1); - __ bind(&join); - LeaveHeapScope(); - __ emit(OP_POP_PRI); - __ emit(OP_JNZ, &start); - } else { - EmitTest(cond, true, &start); - } + if (0 /*cond->tree_has_heap_allocs()*/) { + // Need to create a temporary heap scope here. + Label on_true, join; + EnterHeapScope(Flow_None); + EmitTest(cond, true, &on_true); + __ emit(OP_PUSH_C, 0); + __ emit(OP_JUMP, &join); + __ bind(&on_true); + __ emit(OP_PUSH_C, 1); + __ bind(&join); + LeaveHeapScope(); + __ emit(OP_POP_PRI); + __ emit(OP_JNZ, &start); + } else { + EmitTest(cond, true, &start); } } else { __ bind(&loop_cx.continue_to); - if (cond->tree_has_heap_allocs()) { + if (0 /*cond->tree_has_heap_allocs()*/) { // Need to create a temporary heap scope here. Label on_true, join; EnterHeapScope(Flow_None); @@ -1571,17 +1576,14 @@ CodeGenerator::EmitDoWhileStmt(DoWhileStmt* stmt) } else { EmitTest(cond, false, &loop_cx.break_to); } - EmitStmt(body); - if (!body->IsTerminal()) - __ emit(OP_JUMP, &loop_cx.continue_to); + EmitBlock(body); + __ emit(OP_JUMP, &loop_cx.continue_to); } __ bind(&loop_cx.break_to); } -void -CodeGenerator::EmitLoopControl(int token) -{ +void CodeGenerator::EmitLoopControl(int token) { assert(loop_); assert(token == tBREAK || token == tCONTINUE); @@ -1600,6 +1602,7 @@ CodeGenerator::EmitLoopControl(int token) __ emit(OP_JUMP, &loop_->continue_to); } +#if 0 void CodeGenerator::EmitForStmt(ForStmt* stmt) { @@ -1735,63 +1738,70 @@ CodeGenerator::EmitSwitchStmt(SwitchStmt* stmt) __ bind(&exit_label); } +#endif -void CodeGenerator::EmitFunctionDecl(FunctionDecl* info) { - ke::SaveAndSet set_fun(&fun_, info); - - // Minimum 16 cells for general slack. - current_memory_ = 16; - max_func_memory_ = current_memory_; +void CodeGenerator::EmitFunction(ir::Function* fun) { + assert(fun->is_live()); - if (!info->is_live()) + FunctionDecl* decl = fun->decl(); + if (decl->is_native()) return; - if (info->canonical() == info) - cc_.functions().emplace(info); + assert(fun->body()); - if (!info->body()) - return; + ke::SaveAndSet set_fun(&fun_, fun); - __ bind(&info->cg()->label); + // Minimum 16 cells for general slack. + current_memory_ = 16; + max_func_memory_ = current_memory_; + + __ bind(&fun->label()); __ emit(OP_PROC); - AddDebugLine(info->pos().line); + AddDebugLine(fun->decl()->pos().line); EmitBreak(); current_stack_ = 0; { AutoEnterScope arg_scope(this, &local_syms_); +#if 0 for (const auto& fun_arg : info->args()) EnqueueDebugSymbol(fun_arg, asm_.position()); +#endif - EmitStmt(info->body()); + pushstacklist(); + EmitBlock(fun->body()); + popstacklist(false); } assert(!has_stack_or_heap_scopes()); + // :TODO: remove this! + __ emit(OP_CONST_PRI, 0); + __ emit(OP_RETN); + // If return keyword is missing, we added it in the semantic pass. __ emit(OP_ENDPROC); stack_scopes_.clear(); heap_scopes_.clear(); - info->cg()->pcode_end = asm_.pc(); - info->cg()->max_local_stack = max_func_memory_; + fun->set_pcode_end(asm_.pc()); + fun->set_max_local_stack(max_func_memory_); // In case there is no callgraph, we still need to track which function has // the biggest stack. max_script_memory_ = std::max(max_script_memory_, max_func_memory_); } -void -CodeGenerator::EmitBreak() -{ +void CodeGenerator::EmitBreak() { if (last_break_op_ && *last_break_op_ == asm_.position()) return; __ emit(OP_BREAK); last_break_op_.init(asm_.position()); } +#if 0 void CodeGenerator::EmitEnumStructDecl(EnumStructDecl* decl) { @@ -1811,23 +1821,24 @@ CodeGenerator::EmitMethodmapDecl(MethodmapDecl* decl) for (const auto& method : decl->methods()) EmitFunctionDecl(method); } +#endif -void CodeGenerator::EmitCall(FunctionDecl* fun, cell nargs) { +void CodeGenerator::EmitCall(ir::Function* fun, cell nargs) { assert(fun->is_live()); - if (fun->is_native()) { - if (!fun->cg()->label.bound()) { - __ bind_to(&fun->cg()->label, native_list_.size()); + if (fun->decl()->is_native()) { + if (!fun->label().bound()) { + __ bind_to(&fun->label(), native_list_.size()); native_list_.emplace_back(fun); } - __ sysreq_n(&fun->cg()->label, nargs); + __ sysreq_n(&fun->label(), nargs); } else { __ emit(OP_PUSH_C, nargs); - __ emit(OP_CALL, &fun->cg()->label); + __ emit(OP_CALL, &fun->label()); auto node = callgraph_.find(fun_); if (node == callgraph_.end()) - callgraph_.emplace(fun_, tr::vector{fun}); + callgraph_.emplace(fun_, tr::vector{fun}); else node->second.emplace_back(fun); } @@ -1835,6 +1846,7 @@ void CodeGenerator::EmitCall(FunctionDecl* fun, cell nargs) { max_func_memory_ = std::max(max_func_memory_, current_memory_ + nargs); } +#if 0 void CodeGenerator::EmitDefaultArray(Expr* expr, ArgDecl* arg) { @@ -1939,90 +1951,63 @@ CodeGenerator::EmitUserOp(const UserOperation& user_op, value* lval) } } } +#endif -void CodeGenerator::EmitInc(const value* lval) -{ - switch (lval->ident) { - case iARRAYCELL: - __ emit(OP_INC_I); - break; - case iARRAYCHAR: - __ emit(OP_PUSH_PRI); - __ emit(OP_PUSH_ALT); - __ emit(OP_MOVE_ALT); - __ emit(OP_LODB_I, 1); - __ emit(OP_INC_PRI); - __ emit(OP_STRB_I, 1); - __ emit(OP_POP_ALT); - __ emit(OP_POP_PRI); - break; - case iACCESSOR: - __ emit(OP_INC_PRI); - InvokeSetter(lval->accessor(), false); - break; - case iVARIABLE: { - if (lval->type()->isReference()) { - auto var = lval->sym->as(); +void CodeGenerator::EmitIncDecInner(ir::Lvalue* lval, int token) { + switch (lval->kind()) { + case IrKind::IndexOp: { + auto op = lval->to(); + if (!op->type()->isChar()) { + if (token == tINC) + __ emit(OP_INC_I); + else + __ emit(OP_DEC_I); + } else { __ emit(OP_PUSH_PRI); - __ emit(OP_LREF_S_PRI, var->addr()); - __ emit(OP_INC_PRI); - __ emit(OP_SREF_S_PRI, var->addr()); + __ emit(OP_PUSH_ALT); + __ emit(OP_MOVE_ALT); + __ emit(OP_LODB_I, 1); + if (token == tINC) + __ emit(OP_INC_PRI); + else + __ emit(OP_DEC_PRI); + __ emit(OP_STRB_I, 1); + __ emit(OP_POP_ALT); __ emit(OP_POP_PRI); - break; } - [[fallthrough]]; - } - default: { - auto var = lval->sym->as(); - if (var->vclass() == sLOCAL || var->vclass() == sARGUMENT) - __ emit(OP_INC_S, var->addr()); - else - __ emit(OP_INC, var->addr()); break; } - } -} -void CodeGenerator::EmitDec(const value* lval) -{ - switch (lval->ident) { - case iARRAYCELL: - __ emit(OP_DEC_I); - break; - case iARRAYCHAR: - __ emit(OP_PUSH_PRI); - __ emit(OP_PUSH_ALT); - __ emit(OP_MOVE_ALT); - __ emit(OP_LODB_I, 1); - __ emit(OP_DEC_PRI); - __ emit(OP_STRB_I, 1); - __ emit(OP_POP_ALT); - __ emit(OP_POP_PRI); - break; - case iACCESSOR: - __ emit(OP_DEC_PRI); - InvokeSetter(lval->accessor(), false); - break; - case iVARIABLE: { - if (lval->type()->isReference()) { - auto var = lval->sym->as(); + case IrKind::VariableRef: { + auto ref = lval->as(); + if (ref->type()->isReference()) { __ emit(OP_PUSH_PRI); - __ emit(OP_LREF_S_PRI, var->addr()); - __ emit(OP_DEC_PRI); - __ emit(OP_SREF_S_PRI, var->addr()); + __ emit(OP_LREF_S_PRI, ref->var()->addr()); + if (token == tINC) + __ emit(OP_INC_PRI); + else + __ emit(OP_DEC_PRI); + __ emit(OP_SREF_S_PRI, ref->var()->addr()); __ emit(OP_POP_PRI); - break; + } else { + auto decl = ref->var()->decl(); + if (decl->vclass() == sLOCAL || decl->vclass() == sARGUMENT) { + if (token == tINC) + __ emit(OP_INC_S, ref->var()->addr()); + else + __ emit(OP_DEC_S, ref->var()->addr()); + } else { + if (token == tINC) + __ emit(OP_INC, ref->var()->addr()); + else + __ emit(OP_DEC, ref->var()->addr()); + } } - [[fallthrough]]; - } - default: { - auto var = lval->sym->as(); - if (var->vclass() == sLOCAL || var->vclass() == sARGUMENT) - __ emit(OP_DEC_S, var->addr()); - else - __ emit(OP_DEC, var->addr()); break; } + + default: + assert(false); } } @@ -2105,12 +2090,10 @@ CodeGenerator::pushstacklist() EnterMemoryScope(stack_scopes_); } -int -CodeGenerator::markstack(ParseNode* node, MemuseType type, int size) -{ +cell_t CodeGenerator::markstack(ParseNode* node, MemuseType type, int size) { current_stack_ += size; AllocInScope(node, stack_scopes_.back(), type, size); - return size; + return -current_stack_ * sizeof(cell); } void @@ -2144,8 +2127,8 @@ CodeGenerator::popstacklist(bool codegen) current_stack_ -= PopScope(stack_scopes_); } -void CodeGenerator::LinkPublicFunction(FunctionDecl* decl, uint32_t id) { - __ bind_to(&decl->cg()->funcid, id); +void CodeGenerator::LinkPublicFunction(ir::Function* fun, uint32_t id) { + __ bind_to(&fun->public_id(), id); } int CodeGenerator::DynamicMemorySize() const { @@ -2188,14 +2171,14 @@ CodeGenerator::AutoEnterScope::~AutoEnterScope() { } bool CodeGenerator::ComputeStackUsage(CallGraph::iterator caller_iter) { - FunctionDecl* caller = caller_iter->first; - tr::vector targets = std::move(caller_iter->second); + ir::Function* caller = caller_iter->first; + tr::vector targets = std::move(caller_iter->second); caller_iter = callgraph_.erase(caller_iter); int max_child_stack = 0; while (!targets.empty()) { - FunctionDecl* target = ke::PopBack(&targets); - if (!target->cg()->max_callee_stack) { + auto target = ke::PopBack(&targets); + if (!target->max_callee_stack()) { auto iter = callgraph_.find(target); if (iter != callgraph_.end()) { if (!ComputeStackUsage(iter)) @@ -2203,8 +2186,8 @@ bool CodeGenerator::ComputeStackUsage(CallGraph::iterator caller_iter) { } } - auto local_stack = target->cg()->max_local_stack; - auto callee_stack = target->cg()->max_callee_stack; + auto local_stack = target->max_local_stack(); + auto callee_stack = target->max_callee_stack(); if (!ke::IsUint32AddSafe(local_stack, callee_stack) || local_stack + callee_stack >= kMaxCells) { @@ -2216,11 +2199,11 @@ bool CodeGenerator::ComputeStackUsage(CallGraph::iterator caller_iter) { // Assign this each iteration so we at least have something useful if // we hit a recursive case. - caller->cg()->max_callee_stack = max_child_stack; + caller->set_max_callee_stack(max_child_stack); } - auto local_stack = caller->cg()->max_local_stack; - auto callee_stack = caller->cg()->max_callee_stack; + auto local_stack = caller->max_local_stack(); + auto callee_stack = caller->max_callee_stack(); if (!ke::IsUint32AddSafe(local_stack, callee_stack) || local_stack + callee_stack >= kMaxCells) { @@ -2228,8 +2211,7 @@ bool CodeGenerator::ComputeStackUsage(CallGraph::iterator caller_iter) { return false; } - max_script_memory_ = std::max(caller->cg()->max_local_stack + - caller->cg()->max_callee_stack, + max_script_memory_ = std::max(caller->max_local_stack() + caller->max_callee_stack(), max_script_memory_); return true; } diff --git a/compiler/code-generator.h b/compiler/code-generator.h index 479b4bca7..923aa1ffa 100644 --- a/compiler/code-generator.h +++ b/compiler/code-generator.h @@ -28,6 +28,7 @@ #include "stl/stl-unordered-map.h" #include "data-queue.h" #include "errors.h" +#include "ir.h" #include "parse-node.h" #include "smx-assembly-buffer.h" @@ -40,14 +41,15 @@ class ParseTree; class CodeGenerator final { public: - CodeGenerator(CompileContext& cc, ParseTree* tree); + CodeGenerator(CompileContext& cc, std::shared_ptr mod); bool Generate(); - void LinkPublicFunction(FunctionDecl* decl, uint32_t id); + void LinkPublicFunction(ir::Function* fun, uint32_t id); + std::shared_ptr mod() const { return mod_; } const tr::vector& debug_strings() const { return debug_strings_; } - const tr::vector& native_list() const { return native_list_; } + const tr::vector& native_list() const { return native_list_; } const uint8_t* code_ptr() const { return asm_.bytes(); } uint32_t code_size() const { return (uint32_t)asm_.size(); } @@ -58,13 +60,19 @@ class CodeGenerator final private: // Statements/decls. + void EmitFunction(ir::Function* fun); + void EmitBlock(ir::StreamNode* list); + void EmitInsn(ir::StreamNode* node); + void EmitValueInsn(ir::ValueInsn* insn); + void EmitDoWhile(ir::DoWhile* loop); + void EmitIf(ir::If* insn); void EmitStmtList(StmtList* list); void EmitStmt(Stmt* stmt); void EmitChangeScopeNode(ChangeScopeNode* node); - void EmitVarDecl(VarDeclBase* decl); + void EmitVarDecl(ir::Variable* var); void EmitPstruct(VarDeclBase* decl); void EmitGlobalVar(VarDeclBase* decl); - void EmitLocalVar(VarDeclBase* decl); + void EmitLocalVar(ir::Variable* var); void EmitIfStmt(IfStmt* stmt); void EmitDeleteStmt(DeleteStmt* stmt); void EmitDoWhileStmt(DoWhileStmt* stmt); @@ -73,36 +81,59 @@ class CodeGenerator final void EmitFunctionDecl(FunctionDecl* info); void EmitEnumStructDecl(EnumStructDecl* info); void EmitMethodmapDecl(MethodmapDecl* info); - void EmitReturnStmt(ReturnStmt* stmt); - void EmitReturnArrayStmt(ReturnStmt* stmt); + void EmitReturn(ir::Return* node); + void EmitReturnArray(ir::Return* node); // Expressions. void EmitExpr(Expr* expr); + void EmitValue(ir::Value* val); + void EmitLoad(ir::Load* load); + void EmitConst(ir::Const* cv); + void EmitCommaOp(ir::CommaOp* op); + void EmitCallOp(ir::CallOp* call); void EmitTest(Expr* expr, bool jump_on_true, sp::Label* target); - void EmitUnary(UnaryExpr* expr); - void EmitIncDec(IncDecExpr* expr); + void EmitTest(ir::Value* test, bool jump_on_true, sp::Label* target); + void EmitUnary(ir::UnaryOp* op); + void EmitBinary(ir::BinaryOp* op); + void EmitTernaryOp(ir::TernaryOp* op); + void EmitCharArrayLiteral(ir::CharArrayLiteral* val); + void EmitIncDec(ir::IncDecOp* op); + void EmitIncDecInner(ir::Lvalue* lval, int token); void EmitBinary(BinaryExpr* expr); void EmitBinaryInner(int oper_tok, const UserOperation& in_user_op, Expr* left, Expr* right); void EmitLogicalExpr(LogicalExpr* expr); void EmitChainedCompareExpr(ChainedCompareExpr* expr); - void EmitTernaryExpr(TernaryExpr* expr); void EmitSymbolExpr(SymbolExpr* expr); void EmitIndexExpr(IndexExpr* expr); + void EmitIndexOp(ir::IndexOp* op); void EmitFieldAccessExpr(FieldAccessExpr* expr); + void EmitFieldRef(ir::FieldRef* op); void EmitCallExpr(CallExpr* expr); void EmitDefaultArgExpr(DefaultArgExpr* expr); void EmitCallUserOpExpr(CallUserOpExpr* expr); void EmitNewArrayExpr(NewArrayExpr* expr); + // Calculate the address of an lvalue. + // + // A true return indicates the lvalue can only be computed once, for example + // the base or index are not idempotent, and the address is stored in PRI. A + // false value means nothing happened. + bool EmitPrecalcLvalue(ir::Lvalue* lval); + + // Load from an address calculated by EmitPrecalcLvalue. + bool LoadPrecalcLvalue(ir::Lvalue* lval); + // Logical test helpers. bool EmitUnaryExprTest(UnaryExpr* expr, bool jump_on_true, sp::Label* target); + bool EmitUnaryExprTest(ir::UnaryOp* op, bool jump_on_true, sp::Label* target); void EmitLogicalExprTest(LogicalExpr* expr, bool jump_on_true, sp::Label* target); + void EmitLogicalExprTest(ir::BinaryOp* op, bool jump_on_true, sp::Label* target); bool EmitChainedCompareExprTest(ChainedCompareExpr* expr, bool jump_on_true, sp::Label* target); void EmitDefaultArray(Expr* expr, ArgDecl* arg); void EmitUserOp(const UserOperation& user_op, value* lval); - void EmitCall(FunctionDecl* fun, cell nargs); + void EmitCall(ir::Function* fun, cell nargs); void EmitInc(const value* lval); void EmitDec(const value* lval); void InvokeGetter(MethodmapPropertyDecl* method); @@ -177,7 +208,7 @@ class CodeGenerator final // Stack functions void pushstacklist(); void popstacklist(bool codegen); - int markstack(ParseNode* node, MemuseType type, int size); + cell_t markstack(ParseNode* node, MemuseType type, int size); void modheap_for_scope(const MemoryScope& scope); void modstk_for_scope(const MemoryScope& scope); @@ -196,7 +227,7 @@ class CodeGenerator final void AllocInScope(ParseNode* node, MemoryScope& scope, MemuseType type, int size); int PopScope(tr::vector& scope_list); - using CallGraph = tr::unordered_map>; + using CallGraph = tr::unordered_map>; bool ComputeStackUsage(); bool ComputeStackUsage(CallGraph::iterator caller_iter); @@ -218,11 +249,12 @@ class CodeGenerator final private: CompileContext& cc_; ParseTree* tree_; - FunctionDecl* fun_ = nullptr; + std::shared_ptr mod_; + ir::Function* fun_ = nullptr; int max_script_memory_ = 0; tr::vector debug_strings_; - tr::vector native_list_; + tr::vector native_list_; SmxAssemblyBuffer asm_; DataQueue data_; diff --git a/compiler/compile-context.h b/compiler/compile-context.h index c7784bd6c..2ca123cb6 100644 --- a/compiler/compile-context.h +++ b/compiler/compile-context.h @@ -22,6 +22,7 @@ #include #include +#include #include #include "array-data.h" @@ -74,15 +75,15 @@ class CompileContext final TypeManager* types() const { return types_.get(); } StringPool* atoms() { return &atoms_; } - Atom* atom(const std::string& str) { - return atoms_.add(str); - } Atom* atom(const char* str, size_t length) { return atoms_.add(str, length); } Atom* atom(const char* str) { return atoms_.add(str); } + Atom* atom(std::string_view sv) { + return atoms_.add(sv); + } const std::string& default_include() const { return default_include_; } void set_default_include(const std::string& file) { default_include_ = file; } diff --git a/compiler/expressions.cpp b/compiler/expressions.cpp index 9082bbe16..38742e530 100644 --- a/compiler/expressions.cpp +++ b/compiler/expressions.cpp @@ -40,168 +40,6 @@ namespace sp { namespace cc { -/* Function addresses of binary operators for signed operations */ -static const int op1[17] = { - // hier3 - '*', '/', '%', - // hier4 - '+', '-', - // hier5 - tSHL, tSHR, tSHRU, - // hier6 - '&', - // hier7 - '^', - // hier8 - '|', - // hier9 - tlLE, tlGE, '<', '>', - // hier10 - tlEQ, tlNE -}; - -static inline bool MatchOperator(int oper, FunctionDecl* fun, Type* type1, Type* type2, - int numparam) -{ - if (!oper) - numparam = 1; - - const auto& args = fun->args(); - if (args.size() != size_t(numparam)) - return false; - - assert(numparam == 1 || numparam == 2); - Type* types[2] = { type1, type2 }; - - for (int i = 0; i < numparam; i++) { - if (args[i]->type_info().is_varargs) - return false; - if (args[i]->type_info().type != types[i]) - return false; - } - - if (!oper && fun->type() != type2) - return false; - return true; -} - -bool find_userop(SemaContext& sc, int oper, Type* type1, Type* type2, int numparam, - const value* lval, UserOperation* op) -{ - static const char* binoperstr[] = {"*", "/", "%", "+", "-", "", "", "", "", - "", "", "<=", ">=", "<", ">", "==", "!="}; - static const bool binoper_savepri[] = {false, false, false, false, false, false, false, false, - false, false, false, true, true, true, true, false, - false}; - static const char* unoperstr[] = {"!", "-", "++", "--"}; - static const int unopers[] = {'!', '-', tINC, tDEC}; - - char opername[4] = ""; - size_t i; - bool savepri, savealt; - - if (type1->isReference()) - type1 = type1->inner(); - if (type2 && type2->isReference()) - type2 = type2->inner(); - - /* since user-defined operators on untagged operands are forbidden, we have - * a quick exit. - */ - assert(numparam == 1 || numparam == 2); - if (sc.cc().in_preprocessor()) - return false; - if (type1->isInt() && (numparam == 1 || type2->isInt())) - return false; - - savepri = savealt = false; - /* find the name with the operator */ - if (numparam == 2) { - if (oper == 0) { - /* assignment operator: a special case */ - strcpy(opername, "="); - if (lval != NULL && (lval->ident == iARRAYCELL || lval->ident == iARRAYCHAR)) - savealt = true; - } else { - assert((sizeof binoperstr / sizeof binoperstr[0]) == (sizeof op1 / sizeof op1[0])); - for (i = 0; i < sizeof op1 / sizeof op1[0]; i++) { - if (oper == op1[i]) { - strcpy(opername, binoperstr[i]); - savepri = binoper_savepri[i]; - break; - } - } - } - } else { - assert(oper); - assert(numparam == 1); - /* try a select group of unary operators */ - assert((sizeof unoperstr / sizeof unoperstr[0]) == (sizeof unopers / sizeof unopers[0])); - if (opername[0] == '\0') { - for (i = 0; i < sizeof unopers / sizeof unopers[0]; i++) { - if (oper == unopers[i]) { - strcpy(opername, unoperstr[i]); - break; - } - } - } - } - /* if not found, quit */ - if (opername[0] == '\0') - return false; - - // :TODO: restrict this to globals. - auto opername_atom = sc.cc().atom(opername); - Decl* chain = FindSymbol(sc, opername_atom); - if (!chain) - return false; - - FunctionDecl* decl = nullptr; - bool swapparams; - bool is_commutative = commutative(oper); - for (auto iter = chain; iter; iter = iter->next) { - auto fun = iter->as(); - if (!fun) - continue; - fun = fun->canonical(); - - bool matched = MatchOperator(oper, fun, type1, type2, numparam); - bool swapped = false; - if (!matched && is_commutative && type1 != type2 && oper) { - matched = MatchOperator(oper, fun, type2, type1, numparam); - swapped = true; - } - if (matched) { - decl = fun; - swapparams = swapped; - break; - } - } - - if (!decl) - return false; - - /* we don't want to use the redefined operator in the function that - * redefines the operator itself, otherwise the snippet below gives - * an unexpected recursion: - * fixed:operator+(fixed:a, fixed:b) - * return a + b - */ - if (decl == sc.func()) { - report(408); - } - - markusage(decl, uREAD); - - op->sym = decl; - op->oper = oper; - op->paramspassed = (oper == 0) ? 1 : numparam; - op->savepri = savepri; - op->savealt = savealt; - op->swapparams = swapparams; - return true; -} - bool checktag_string(Type* type, const value* sym1) { if (sym1->type()->isArray()) return false; @@ -577,9 +415,7 @@ bool checktag(Type* type, Type* expr_type) { * precautionary "push" of the primary register is scrapped and the constant * is read into the secondary register immediately. */ -int -commutative(int oper) -{ +bool IsOperTokenCommutative(int oper) { switch (oper) { case '+': case '*': diff --git a/compiler/expressions.h b/compiler/expressions.h index 12b13006c..0670d3653 100644 --- a/compiler/expressions.h +++ b/compiler/expressions.h @@ -18,8 +18,6 @@ * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. - * - * Version: $Id$ */ #pragma once @@ -44,10 +42,8 @@ int NextExprOp(Lexer* lexer, int* opidx, int* list); struct UserOperation; bool find_userop(SemaContext& sc, int oper, Type* type1, Type* type2, int numparam, const value* lval, UserOperation* op); -bool find_userop(SemaContext& sc, int oper, int tag1, int tag2, int numparam, - const value* lval, UserOperation* op); -int commutative(int oper); +bool IsOperTokenCommutative(int oper); cell calc(cell left, int oper_tok, cell right, char* boolresult); bool IsValidIndexType(Type* type); bool matchtag(int formaltag, int actualtag, int flags); diff --git a/compiler/ir.cpp b/compiler/ir.cpp new file mode 100644 index 000000000..9e2f49cfc --- /dev/null +++ b/compiler/ir.cpp @@ -0,0 +1,104 @@ +// vim: set ts=8 sts=4 sw=4 tw=99 et: +// +// Copyright (c) AlliedModders 2024 +// +// This software is provided "as-is", without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from +// the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software in +// a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +#include "ir.h" + +namespace sp { +namespace cc { +namespace ir { + +bool Value::HasSideEffects() { + switch (kind_) { + case IrKind::Store: + case IrKind::IncDecOp: + case IrKind::CallOp: + return true; + case IrKind::PropertyRef: { + auto ir = as(); + return ir->val()->HasSideEffects(); + } + case IrKind::TempRef: { + auto ir = as(); + return ir->val()->HasSideEffects(); + } + case IrKind::FieldRef: { + auto ir = as(); + return ir->base()->HasSideEffects(); + } + case IrKind::IndexOp: { + auto ir = as(); + return ir->base()->HasSideEffects() || ir->index()->HasSideEffects(); + } + case IrKind::Load: { + auto ir = as(); + return ir->lval()->HasSideEffects(); + } + case IrKind::TernaryOp: { + auto ir = as(); + return ir->select()->HasSideEffects() || + ir->on_true()->HasSideEffects() || + ir->on_false()->HasSideEffects(); + } + case IrKind::BinaryOp: { + auto ir = as(); + return ir->left()->HasSideEffects() || + ir->right()->HasSideEffects(); + } + case IrKind::CommaOp: { + auto ir = as(); + for (const auto& val : ir->values()) { + if (val->HasSideEffects()) + return true; + } + return false; + } + case IrKind::Array: { + auto ir = as(); + for (const auto& val : ir->values()) { + if (val->HasSideEffects()) + return true; + } + return false; + } + case IrKind::UnaryOp: { + auto ir = as(); + return ir->val()->HasSideEffects(); + } + default: + return false; + } +} + +void Function::AddReferenceTo(Function* other) { + if (!refers_to_) { + auto& cc = CompileContext::get(); + refers_to_ = cc.allocator().alloc>(); + } + for (Function* decl : *refers_to_) { + if (decl == other) + return; + } + refers_to_->emplace_front(other); +} + + +} // namespace ir +} // namespace cc +} // namespace sp diff --git a/compiler/ir.h b/compiler/ir.h new file mode 100644 index 000000000..013f154a6 --- /dev/null +++ b/compiler/ir.h @@ -0,0 +1,767 @@ +// vim: set ts=8 sts=4 sw=4 tw=99 et: +// +// Copyright (c) AlliedModders 2024 +// +// This software is provided "as-is", without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from +// the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software in +// a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +#pragma once + +#include +#include + +#include "ast-types.h" +#include "label.h" +#include "parse-node.h" +#include "pool-objects.h" +#include "qualtype.h" +#include "stl/stl-vector.h" + +namespace sp { +namespace cc { +namespace ir { + +class Function; +class Variable; + +class Node : public PoolObject { + protected: + explicit Node(IrKind kind, ParseNode* pn) + : kind_(kind) + { + pn_u.pn = pn; + } + + public: + ParseNode* pn() const { return pn_u.pn; } + IrKind kind() const { return kind_; } + + template T* as() { + if (T::is_a(this)) + return reinterpret_cast(this); + return nullptr; + } + template T* to() { + assert(T::is_a(this)); + return reinterpret_cast(this); + } + + protected: + IrKind kind_ : 8; + union { + ParseNode* pn; + Expr* expr; + Stmt* stmt; + Decl* decl; + UnaryExpr* unary; + IncDecExpr* inc_dec_expr; + VarDecl* var_decl; + FunctionDecl* fun_decl; + StringExpr* string_expr; + DoWhileStmt* do_while_stmt; + } pn_u; +}; + +class Module final : public std::enable_shared_from_this { + public: + tr::vector& functions() { return functions_; } + tr::vector& globals() { return globals_; } + + private: + tr::vector functions_; + tr::vector globals_; +}; + +class Value : public Node { + public: + Value(IrKind kind, Expr* expr, QualType type) + : Node(kind, expr), + type_(type) + {} + + QualType type() const { return type_; } + + bool HasSideEffects(); + + private: + QualType type_; +}; + +class StreamNode : public Node { + public: + StreamNode(IrKind kind, ParseNode* pn) + : Node(kind, pn) + {} + + StreamNode* next() const { return next_; } + void set_next(StreamNode* node) { next_ = node; } + + private: + StreamNode* next_ = nullptr; +}; + +class NodeListBuilder final { + public: + NodeListBuilder() {} + NodeListBuilder(const NodeListBuilder&) = delete; + NodeListBuilder(NodeListBuilder&&) = delete; + + explicit NodeListBuilder(NodeListBuilder** prev_loc) + : prev_(*prev_loc), + prev_loc_(prev_loc) + { + *prev_loc_ = this; + } + + ~NodeListBuilder() { + if (prev_loc_) { + assert(*prev_loc_ == this); + *prev_loc_ = prev_; + } + } + + void add(StreamNode* node) { + if (!first_) { + first_ = node; + last_ = node; + } else { + last_->set_next(node); + last_ = node; + } + } + + template + T* emplace(Args&&... args) { + auto ir = new T(std::forward(args)...); + add(ir); + return ir; + } + + StreamNode* Finish() { + auto tmp = first_; + first_ = nullptr; + last_ = nullptr; + return tmp; + } + + NodeListBuilder& operator =(const NodeListBuilder) = delete; + NodeListBuilder& operator =(NodeListBuilder&&) = delete; + + private: + NodeListBuilder* prev_ = nullptr; + NodeListBuilder** prev_loc_ = nullptr; + StreamNode* first_ = nullptr; + StreamNode* last_ = nullptr; +}; + +class DeclNode : public StreamNode { + public: + DeclNode(IrKind kind, Decl* decl) + : StreamNode(kind, decl) + {} +}; + +class Variable final : public DeclNode { + public: + explicit Variable(VarDeclBase* var, ir::Value* init) + : DeclNode(IrKind::Variable, var), + init_(init) + {} + + static constexpr cell_t kInvalidAddr = std::numeric_limits::min(); + + cell_t addr() const { + assert(addr_ != kInvalidAddr); + return addr_; + } + void set_addr(cell_t addr) { + assert(addr_ == kInvalidAddr); + addr_ = addr; + } + + VarDeclBase* decl() const { return pn_u.var_decl; } + ir::Value* init() const { return init_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Variable; } + + private: + cell_t addr_ = kInvalidAddr; + ir::Value* init_; +}; + +class Function final : public DeclNode { + public: + explicit Function(FunctionDecl* decl) + : DeclNode(IrKind::Function, decl) + {} + + void AddReferenceTo(ir::Function* other); + + static bool is_a(Node* node) { return node->kind() == IrKind::Function; } + + FunctionDecl* decl() const { return pn_u.fun_decl; } + Label& label() { return label_; } + Label& public_id() { return public_id_; } + PoolForwardList* refers_to() { return refers_to_; } + + StreamNode* body() const { return body_; } + void set_body(StreamNode* body) { body_ = body; } + + uint32_t pcode_end() const { return pcode_end_; } + void set_pcode_end(uint32_t end) { pcode_end_ = end; } + + int32_t max_local_stack() const { return max_local_stack_; } + void set_max_local_stack(int32_t stack) { max_local_stack_ = stack; } + + bool is_live() const { return is_live_; } + void set_is_live() { is_live_ = true; } + + int32_t max_callee_stack() const { return max_callee_stack_; } + void set_max_callee_stack(int32_t value) { max_callee_stack_ = value; } + + private: + StreamNode* body_ = nullptr; + PoolForwardList* refers_to_ = nullptr; + Label label_; + Label public_id_; + uint32_t pcode_end_ = 0; + int32_t max_local_stack_ = 0; + int32_t max_callee_stack_ = 0; + bool is_live_ = false; +}; + +class Return final : public StreamNode { + public: + Return(ReturnStmt* stmt, Value* val) + : StreamNode(IrKind::Return, stmt), + val_(val) + {} + + Value* val() const { return val_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Return; } + + private: + Value* val_; +}; + +class Exit final : public StreamNode { + public: + Exit(ExitStmt* stmt, Value* val) + : StreamNode(IrKind::Exit, stmt) + {} + + Value* val() const { return val_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Exit; } + + private: + Value* val_; +}; + +class Assert final : public StreamNode { + public: + Assert(AssertStmt* stmt, Value* val) + : StreamNode(IrKind::Assert, stmt) + {} + + Value* val() const { return val_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Assert; } + + private: + Value* val_; +}; + +class ValueInsn final : public StreamNode { + public: + ValueInsn(ExprStmt* stmt, Value* val) + : StreamNode(IrKind::ValueInsn, stmt), + val_(val) + {} + + Value* val() const { return val_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::ValueInsn; } + + private: + Value* val_; +}; + +class Break final : public StreamNode { + public: + explicit Break(BreakStmt* stmt) + : StreamNode(IrKind::Break, stmt) + {} + + static bool is_a(Node* node) { return node->kind() == IrKind::Break; } +}; + +class Continue final : public StreamNode { + public: + explicit Continue(ContinueStmt* stmt) + : StreamNode(IrKind::Continue, stmt) + {} + + static bool is_a(Node* node) { return node->kind() == IrKind::Continue; } +}; + +class If final : public StreamNode { + public: + If(IfStmt* stmt, Value* cond, StreamNode* on_true, StreamNode* on_false) + : StreamNode(IrKind::If, stmt), + cond_(cond), + on_true_(on_true), + on_false_(on_false) + {} + + Value* cond() const { return cond_; } + StreamNode* on_true() const { return on_true_; } + StreamNode* on_false() const { return on_false_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::If; } + + private: + Value* cond_; + StreamNode* on_true_; + StreamNode* on_false_; +}; + +class DoWhile final : public StreamNode { + public: + DoWhile(DoWhileStmt* stmt, Value* cond, StreamNode* body) + : StreamNode(IrKind::DoWhile, stmt), + cond_(cond), + body_(body) + {} + + DoWhileStmt* stmt() const { return pn_u.do_while_stmt; } + Value* cond() const { return cond_; } + StreamNode* body() const { return body_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::DoWhile; } + + private: + Value* cond_; + StreamNode* body_; +}; + +class Delete final : public StreamNode { + public: + Delete(DeleteStmt* stmt, Value* val, MethodmapMethodDecl* dtor) + : StreamNode(IrKind::Delete, stmt), + val_(val), + dtor_(dtor) + {} + + Value* val() const { return val_; } + MethodmapMethodDecl* dtor() const { return dtor_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Delete; } + + private: + Value* val_; + MethodmapMethodDecl* dtor_; +}; + +class ForLoop final : public StreamNode { + public: + ForLoop(ForStmt* stmt, StreamNode* init, Value* cond, Value* advance, StreamNode* body) + : StreamNode(IrKind::ForLoop, stmt), + init_(init), + cond_(cond), + advance_(advance), + body_(body) + {} + + StreamNode* init() const { return init_; } + Value* cond() const { return cond_; } + Value* advance() const { return advance_; } + StreamNode* body() const { return body_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::ForLoop; } + + private: + StreamNode* init_; + Value* cond_; + Value* advance_; + StreamNode* body_; +}; + +class Switch final : public StreamNode { + public: + typedef std::pair, StreamNode*> Case; + + Switch(SwitchStmt* stmt, Value* cond, std::vector&& cases, StreamNode* default_case) + : StreamNode(IrKind::Switch, stmt), + cond_(cond), + cases_(std::move(cases)), + default_case_(default_case) + {} + + Value* cond() const { return cond_; } + const PoolArray& cases() const { return cases_; } + StreamNode* default_case() const { return default_case_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Switch; } + + private: + Value* cond_; + PoolArray cases_; + StreamNode* default_case_; +}; + +class Const final : public Value { + public: + Const(Expr* expr, QualType type, cell_t value) + : Value(IrKind::ConstVal, expr, type), + value_(value) + {} + + cell_t value() const { return value_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::ConstVal; } + + private: + cell_t value_; +}; + +class CharArrayLiteral final : public Value { + public: + CharArrayLiteral(StringExpr* expr, QualType type) + : Value(IrKind::CharArrayLiteral, expr, type) + {} + + StringExpr* expr() const { return pn_u.string_expr; } + + static bool is_a(Node* op) { return op->kind() == IrKind::CharArrayLiteral; } +}; + +class UnaryOp final : public Value { + public: + UnaryOp(UnaryExpr* expr, QualType type, Value* val) + : Value(IrKind::UnaryOp, expr, type), + val_(val) + {} + + Value* val() const { return val_; } + UnaryExpr* expr() const { return pn_u.unary; } + + static bool is_a(Node* op) { return op->kind() == IrKind::UnaryOp; } + + private: + Value* val_; +}; + +class CallUserOp final : public Value { + public: + CallUserOp(Expr* expr, QualType type, FunctionDecl* target, Value* first = nullptr, + Value* second = nullptr, bool swapped = false) + : Value(IrKind::CallUserOp, expr, type), + target_(target), + first_(first), + second_(second), + swapped_(swapped) + {} + + FunctionDecl* target() const { return target_; } + Value* first() const { return first_; } + Value* second() const { return second_; } + bool swapped() const { return swapped_; } + + private: + FunctionDecl* target_; + Value* first_; + Value* second_; + bool swapped_; +}; + +class TypeRef final : public Value { + public: + TypeRef(Expr* expr, QualType type) + : Value(IrKind::TypeRef, expr, type) + {} + + static bool is_a(Node* op) { return op->kind() == IrKind::TypeRef; } +}; + +class FunctionRef final : public Value { + public: + FunctionRef(Expr* expr, QualType type, Function* fun) + : Value(IrKind::FunctionRef, expr, type), + fun_(fun) + {} + + Function* fun() const { return fun_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::FunctionRef; } + + private: + Function* fun_; +}; + +class Array final : public Value { + public: + Array(Expr* expr, QualType type, const std::vector& values) + : Value(IrKind::Array, expr, type) + { + new (&values_) decltype(values_)(values); + } + + const PoolArray& values() const { return values_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::Array; } + + private: + PoolArray values_; +}; + +class CommaOp final : public Value { + public: + CommaOp(Expr* expr, QualType type, const std::vector& values) + : Value(IrKind::CommaOp, expr, type) + { + new (&values_) decltype(values_)(values); + } + + const PoolArray& values() const { return values_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::CommaOp; } + + private: + PoolArray values_; +}; + +class CallOp final : public Value { + public: + CallOp(Expr* expr, QualType type, ir::Function* target, const std::vector& values) + : Value(IrKind::CallOp, expr, type), + target_(target) + { + new (&values_) decltype(values_)(values); + } + + ir::Function* target() const { return target_; } + const PoolArray& argv() const { return values_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::CallOp; } + + private: + ir::Function* target_; + PoolArray values_; +}; + +class BinaryOp final : public Value { + public: + BinaryOp(Expr* expr, QualType type, int token, Value* left, Value* right) + : Value(IrKind::BinaryOp, expr, type), + token_(token), + left_(left), + right_(right) + {} + + Value* left() const { return left_; } + Value* right() const { return right_; } + int token() const { return token_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::BinaryOp; } + + private: + int token_; + Value* left_; + Value* right_; +}; + +class TernaryOp final : public Value { + public: + TernaryOp(Expr* expr, QualType type, Value* select, Value* on_true, Value* on_false) + : Value(IrKind::TernaryOp, expr, type), + select_(select), + on_true_(on_true), + on_false_(on_false) + {} + + Value* select() const { return select_; } + Value* on_true() const { return on_true_; } + Value* on_false() const { return on_false_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::TernaryOp; } + + private: + Value* select_; + Value* on_true_; + Value* on_false_; +}; + +class ThisRef final : public Value { + public: + ThisRef(ThisExpr* expr, QualType type) + : Value(IrKind::ThisRef, expr, type) + {} +}; + +class Lvalue : public Value { + public: + Lvalue(IrKind kind, Expr* expr, QualType type) + : Value(kind, expr, type) + {} + + static bool is_a(Node* op) { + return op->kind() == IrKind::VariableRef || + op->kind() == IrKind::IndexOp || + op->kind() == IrKind::TempRef || + op->kind() == IrKind::PropertyRef || + op->kind() == IrKind::FieldRef; + } +}; + +class IncDecOp final : public Value { + public: + IncDecOp(IncDecExpr* expr, QualType type, Lvalue* lval, Value* getter) + : Value(IrKind::IncDecOp, expr, type), + lval_(lval), + getter_(getter) + {} + + IncDecExpr* expr() const { return pn_u.inc_dec_expr; } + Lvalue* lval() const { return lval_; } + Value* getter() const { return getter_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::IncDecOp; } + + private: + Lvalue* lval_; + Value* getter_; +}; + +class Load final : public Value { + public: + Load(Expr* expr, QualType type, Lvalue* lval) + : Value(IrKind::Load, expr, type), + lval_(lval) + {} + + Lvalue* lval() const { return lval_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::Load; } + + private: + Lvalue* lval_; +}; + +class Store final : public Value { + public: + Store(Expr* expr, QualType type, Lvalue* loc, Value* val) + : Value(IrKind::Store, expr, type), + loc_(loc), + val_(val) + {} + + Lvalue* loc() const { return loc_; } + Value* val() const { return val_; } + + private: + Lvalue* loc_; + Value* val_; +}; + +class VariableRef final : public Lvalue { + public: + VariableRef(Expr* expr, QualType type, Variable* var) + : Lvalue(IrKind::VariableRef, expr, type), + var_(var) + {} + + Variable* var() const { return var_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::VariableRef; } + + private: + Variable* var_; +}; + +class IndexOp final : public Lvalue { + public: + IndexOp(Expr* expr, QualType type, Value* base, Value* index) + : Lvalue(IrKind::IndexOp, expr, type), + base_(base), + index_(index) + {} + + Value* base() const { return base_; } + Value* index() const { return index_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::IndexOp; } + + private: + Value* base_; + Value* index_; +}; + +class FieldRef final : public Lvalue { + public: + FieldRef(Expr* expr, QualType type, Value* base, LayoutFieldDecl* field) + : Lvalue(IrKind::FieldRef, expr, type), + base_(base), + field_(field) + {} + + Value* base() const { return base_; } + LayoutFieldDecl* field() const { return field_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::FieldRef; } + + private: + Value* base_; + LayoutFieldDecl* field_; +}; + +class TempRef final : public Lvalue { + public: + TempRef(Expr* expr, Value* val) + : Lvalue(IrKind::TempRef, expr, val->type()), + val_(val) + {} + + Value* val() const { return val_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::TempRef; } + + private: + Value* val_; +}; + +class PropertyRef final : public Lvalue { + public: + PropertyRef(Expr* expr, QualType type, Value* val, MethodmapPropertyDecl* decl) + : Lvalue(IrKind::PropertyRef, expr, type), + val_(val), + decl_(decl) + {} + + Value* val() const { return val_; } + MethodmapPropertyDecl* decl() const { return decl_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::PropertyRef; } + + private: + Value* val_; + MethodmapPropertyDecl* decl_; +}; + +} // namespace ir +} // namespace cc +} // namespace sp diff --git a/compiler/lexer.cpp b/compiler/lexer.cpp index 45bcddd73..95a8c306d 100644 --- a/compiler/lexer.cpp +++ b/compiler/lexer.cpp @@ -299,7 +299,7 @@ void Lexer::lex_float(full_token_t* tok, cell_t whole) { tok->id = tRATIONAL; } -int Lexer::preproc_expr(cell* val, Type** type) { +int Lexer::preproc_expr(cell* val, QualType* type) { ke::SaveAndSet forbid_const(&cc_.in_preprocessor(), true); return Parser::PreprocExpr(val, type); /* get value (or 0 on error) */ } @@ -331,7 +331,8 @@ void Lexer::HandleDirectives() { skiplevel_ = ifstack_.size(); cell val = 0; - preproc_expr(&val, NULL); /* get value (or 0 on error) */ + QualType ignore_type; + preproc_expr(&val, &ignore_type); /* get value (or 0 on error) */ CheckLineEmpty(); ifstack_.back() = (char)(val ? PARSEMODE : SKIPMODE); diff --git a/compiler/lexer.h b/compiler/lexer.h index 8a5e46d97..902ccfaac 100644 --- a/compiler/lexer.h +++ b/compiler/lexer.h @@ -397,7 +397,7 @@ class Lexer full_token_t* advance_token_ptr(); full_token_t* next_token(); void lexpop(); - int preproc_expr(cell* val, Type** type); + int preproc_expr(cell* val, QualType* type); void substallpatterns(unsigned char* line, int buffersize); bool substpattern(unsigned char* line, size_t buffersize, const char* pattern, const char* substitution, int& patternLen, int& substLen); diff --git a/compiler/main.cpp b/compiler/main.cpp index 2d0b7ec3c..a6b09bd48 100644 --- a/compiler/main.cpp +++ b/compiler/main.cpp @@ -135,6 +135,8 @@ int RunCompiler(int argc, char** argv, CompileContext& cc) { setcaption(); setconfig(argv[0]); /* the path to the include files */ + auto mod = std::make_shared(); + if (options->source_files.size() > 1) { report(452); goto cleanup; @@ -168,7 +170,7 @@ int RunCompiler(int argc, char** argv, CompileContext& cc) { cc.lexer()->Init(); { - Semantics sema(cc); + Semantics sema(cc, mod); Parser parser(cc, &sema); AutoCountErrors errors; @@ -192,9 +194,11 @@ int RunCompiler(int argc, char** argv, CompileContext& cc) { sema.set_context(nullptr); errors.Reset(); - if (!sema.Analyze(tree) || !errors.ok()) + if (!sema.Analyze(tree)) goto cleanup; + assert(errors.ok()); + tree->stmts()->ProcessUses(sc); ok = true; } @@ -211,7 +215,7 @@ int RunCompiler(int argc, char** argv, CompileContext& cc) { cc.set_shutting_down(); cc.reports()->DumpErrorReport(true); - CodeGenerator cg(cc, tree); + CodeGenerator cg(cc, mod); if (tree && compile_ok) compile_ok = cg.Generate(); diff --git a/compiler/messages.h b/compiler/messages.h index fd746b0cd..a691756fc 100644 --- a/compiler/messages.h +++ b/compiler/messages.h @@ -56,7 +56,7 @@ static const char* errmsg[] = { /*029*/ "invalid expression, assumed zero\n", /*030*/ "compound statement not closed at the end of file (started at line %d)\n", /*031*/ "unknown directive\n", - /*032*/ "array index out of bounds (variable \"%s\")\n", + /*032*/ "array index out of bounds\n", /*033*/ "array must be indexed (variable \"%s\")\n", /*034*/ "argument does not have a default value (argument %d)\n", /*035*/ "argument type mismatch (argument %d)\n", @@ -214,7 +214,7 @@ static const char* errmsg[] = { /*176*/ "non-static method or property '%s' must be called with a value of type '%s'\n", /*177*/ "static method '%s' must be invoked via its type (try '%s.%s')\n", /*178*/ "cannot coerce %s[] to %s[]; storage classes differ\n", - /*179*/ "cannot assign %s[] to %s[], storage classes differ\n", + /*179*/ "unused179\n", /*180*/ "function return type differs from prototype. expected '%s', but got '%s'\n", /*181*/ "function argument named '%s' differs from prototype\n", /*182*/ "functions that return arrays cannot be used as callbacks\n", @@ -336,4 +336,7 @@ static const char* errmsg_ex[] = { /*450*/ "no viable conversion from \"%s\" to \"%s\"\n", /*451*/ "function %s returns an array but return type is not marked as an array\n", /*452*/ "multiple command-line source files are no longer supported\n", + /*453*/ "operator \"%s\" not defined for type \"%s\"\n", + /*454*/ "type \"%s\" is not a scalar type\n", + /*455*/ "illegal assignment (not an l-value)\n", }; diff --git a/compiler/name-resolution.cpp b/compiler/name-resolution.cpp index 2e3adf638..bd2f4e627 100644 --- a/compiler/name-resolution.cpp +++ b/compiler/name-resolution.cpp @@ -202,9 +202,9 @@ bool EnumDecl::EnterNames(SemaContext& sc) { AutoErrorPos error_pos(field->pos()); if (field->value() && field->value()->Bind(sc) && sc.sema()->CheckExpr(field->value())) { - Type* field_type = nullptr; - if (field->value()->EvalConst(&value, &field_type)) - matchtag(type_, field_type, MATCHTAG_COERCE | MATCHTAG_ENUM_ASSN); + QualType field_type; + if (Expr::EvalConst(field->value(), &value, &field_type)) + matchtag(type_, *field_type, MATCHTAG_COERCE | MATCHTAG_ENUM_ASSN); else error(field->pos(), 80); } @@ -368,9 +368,7 @@ bool ConstDecl::EnterNames(SemaContext& sc) { return true; } -bool -ConstDecl::Bind(SemaContext& sc) -{ +bool ConstDecl::Bind(SemaContext& sc) { if (sc.func() && !EnterNames(sc)) return false; @@ -379,17 +377,20 @@ ConstDecl::Bind(SemaContext& sc) if (!expr_->Bind(sc)) return false; - if (!sc.sema()->CheckExpr(expr_)) + auto val = sc.sema()->CheckExpr(expr_); + if (!val) return false; - Type* type; - if (!expr_->EvalConst(&value_, &type)) { + auto cv = val->as(); + if (!cv) { report(expr_, 8); return false; } AutoErrorPos aep(pos_); - matchtag(type_.type, type, 0); + matchtag(type_.type, *cv->type(), 0); + + value_ = cv->value(); return true; } @@ -850,18 +851,18 @@ FunctionDecl::BindArgs(SemaContext& sc) var->set_default_value(new DefaultArg()); cell val; - Type* type; - if (!init->EvalConst(&val, &type)) { + QualType type; + if (!Expr::EvalConst(init, &val, &type)) { error(var->pos(), 8); // Populate to avoid errors. val = 0; - type = typeinfo.type; + type = QualType(typeinfo.type); } - var->default_value()->type = type; + var->default_value()->type = *type; var->default_value()->val = ke::Some(val); - matchtag(var->type(), type, MATCHTAG_COERCE); + matchtag(var->type(), *type, MATCHTAG_COERCE); } } diff --git a/compiler/parse-node.cpp b/compiler/parse-node.cpp index aad10c120..3e1b5608e 100644 --- a/compiler/parse-node.cpp +++ b/compiler/parse-node.cpp @@ -63,23 +63,6 @@ ParseNode::error(const token_pos_t& pos, int number) report(pos, number); } -void -Expr::FlattenLogical(int token, std::vector* out) -{ - out->push_back(this); -} - -void -LogicalExpr::FlattenLogical(int token, std::vector* out) -{ - if (token_ == token) { - left_->FlattenLogical(token, out); - right_->FlattenLogical(token, out); - } else { - Expr::FlattenLogical(token, out); - } -} - bool Stmt::IsTerminal() const { switch (flow_type()) { case Flow_Break: diff --git a/compiler/parse-node.h b/compiler/parse-node.h index dc2b4efee..93ea2fb39 100644 --- a/compiler/parse-node.h +++ b/compiler/parse-node.h @@ -588,26 +588,13 @@ class Expr : public ParseNode can_alloc_heap_(false) {} - // Flatten a series of binary expressions into a single list. - virtual void FlattenLogical(int token, std::vector* out); - - // Fold the expression into a constant. The expression must have been - // bound and analyzed. False indicates the expression is non-constant. - // - // If an expression folds constants during analysis, it can return false - // here. ExprToConst handles both cases. - virtual bool FoldToConstant() { - return false; - } - // Process any child nodes whose value is consumed. virtual void ProcessUses(SemaContext& sc) = 0; // Process any child nodes whose value is not consumed. virtual void ProcessDiscardUses(SemaContext& sc) { ProcessUses(sc); } - // Evaluate as a constant. Returns false if non-const. This is a wrapper - // around FoldToConstant(). - bool EvalConst(cell* value, Type** type); + // Evaluate as a constant. Returns false if non-const. + static bool EvalConst(Expr* expr, cell* value, QualType* type); // Return whether or not the expression is idempotent (eg has side effects). bool HasSideEffects(); @@ -700,7 +687,7 @@ class BinaryExpr final : public BinaryExprBase public: BinaryExpr(const token_pos_t& pos, int token, Expr* left, Expr* right); - bool FoldToConstant() override; + bool ConstantFold(cell* value, QualType* type); static bool is_a(Expr* node) { return node->kind() == ExprKind::BinaryExpr; } @@ -731,8 +718,6 @@ class LogicalExpr final : public BinaryExprBase : BinaryExprBase(ExprKind::LogicalExpr, pos, token, left, right) {} - void FlattenLogical(int token, std::vector* out) override; - static bool is_a(Expr* node) { return node->kind() == ExprKind::LogicalExpr; } }; @@ -787,7 +772,6 @@ class TernaryExpr final : public Expr ok &= third_->Bind(sc); return ok; } - bool FoldToConstant() override; void ProcessUses(SemaContext& sc) override; void ProcessDiscardUses(SemaContext& sc) override; @@ -927,17 +911,21 @@ class NamedArgExpr : public Expr public: NamedArgExpr(const token_pos_t& pos, Atom* name, Expr* expr) : Expr(ExprKind::NamedArgExpr, pos), - name(name), - expr(expr) + name_(name), + expr_(expr) {} - bool Bind(SemaContext& sc) override { return expr->Bind(sc); } - void ProcessUses(SemaContext& sc) override { expr->ProcessUses(sc); } + bool Bind(SemaContext& sc) override { return expr_->Bind(sc); } + void ProcessUses(SemaContext& sc) override { expr_->ProcessUses(sc); } static bool is_a(Expr* node) { return node->kind() == ExprKind::NamedArgExpr; } - Atom* name; - Expr* expr; + Atom* name() const { return name_; } + Expr* expr() const { return expr_; } + + private: + Atom* name_; + Expr* expr_; }; class CallExpr final : public Expr diff --git a/compiler/parser.cpp b/compiler/parser.cpp index c6f004351..cadbcd87b 100644 --- a/compiler/parser.cpp +++ b/compiler/parser.cpp @@ -299,10 +299,11 @@ Parser::parse_unknown_decl(const full_token_t* tok) return nullptr; } -bool Parser::PreprocExpr(cell* val, Type** type) { +bool Parser::PreprocExpr(cell* val, QualType* type) { auto& cc = CompileContext::get(); - Semantics sema(cc); + // :TODO: fix this so we don't use Sema. + Semantics sema(cc, nullptr); Parser parser(cc, &sema); auto expr = parser.hier14(); if (!expr) @@ -315,7 +316,8 @@ bool Parser::PreprocExpr(cell* val, Type** type) { if (!expr->Bind(sc) || !sema.CheckExpr(expr)) return false; - return expr->EvalConst(val, type); + + return Expr::EvalConst(expr, val, type); } Stmt* diff --git a/compiler/parser.h b/compiler/parser.h index c2896ba76..f13a901c1 100644 --- a/compiler/parser.h +++ b/compiler/parser.h @@ -38,7 +38,7 @@ class Parser Parser(CompileContext& cc, Semantics* sema); ~Parser(); - static bool PreprocExpr(cell* val, Type** type); + static bool PreprocExpr(cell* val, QualType* type); ParseTree* Parse(); diff --git a/compiler/pool-objects.h b/compiler/pool-objects.h index 41b48ab17..a9d724ecb 100644 --- a/compiler/pool-objects.h +++ b/compiler/pool-objects.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include "compile-context.h" @@ -137,13 +138,13 @@ template using PoolForwardList = std::forward_list>; struct KeywordTablePolicy { - static bool matches(const sp::CharsAndLength& a, const sp::CharsAndLength& b) { - if (a.length() != b.length()) + static bool matches(const std::string_view& a, const std::string_view& b) { + if (a.size() != b.size()) return false; - return strncmp(a.str(), b.str(), a.length()) == 0; + return strncmp(a.data(), b.data(), a.size()) == 0; } - static uint32_t hash(const sp::CharsAndLength& key) { - return ke::HashCharSequence(key.str(), key.length()); + static uint32_t hash(const std::string_view& key) { + return ke::HashCharSequence(key.data(), key.size()); } }; diff --git a/compiler/qualtype.h b/compiler/qualtype.h index e12f1228c..573e3c546 100644 --- a/compiler/qualtype.h +++ b/compiler/qualtype.h @@ -30,12 +30,18 @@ class Type; // Compact encoding of type + constness. class QualType { public: + QualType() : impl_(nullptr) {} explicit QualType(Type* type) { impl_ = type; } - explicit QualType(Type* type, bool is_const) { + QualType(Type* type, bool is_const) { impl_ = ke::SetPointerBits(type, is_const ? 1 : 0); } + QualType(QualType type, bool is_const) + : impl_(type.impl_) + { + impl_ = ke::SetPointerBits(impl_, is_const ? 1 : 0); + } bool is_const() const { return ke::GetPointerBits<2>(impl_) == 1; @@ -50,6 +56,7 @@ class QualType { uint32_t hash() const { return ke::HashPointer(impl_); } + explicit operator bool() const { return impl_ != nullptr; } bool operator ==(const QualType& other) const { return impl_ == other.impl_; } bool operator !=(const QualType& other) const { return impl_ != other.impl_; } diff --git a/compiler/semantics.cpp b/compiler/semantics.cpp index c9a83e1eb..71ce21e9c 100644 --- a/compiler/semantics.cpp +++ b/compiler/semantics.cpp @@ -22,12 +22,14 @@ #include "semantics.h" #include +#include #include #include "array-helpers.h" #include "code-generator.h" #include "errors.h" #include "expressions.h" +#include "ir.h" #include "lexer.h" #include "parse-node.h" #include "sctracker.h" @@ -38,11 +40,11 @@ namespace sp { namespace cc { -Semantics::Semantics(CompileContext& cc) - : cc_(cc) -{ - types_ = cc.types(); -} +Semantics::Semantics(CompileContext& cc, std::shared_ptr mod) + : cc_(cc), + types_(cc.types()), + mod_(std::move(mod)) +{} bool Semantics::Analyze(ParseTree* tree) { SemaContext sc(this); @@ -55,6 +57,11 @@ bool Semantics::Analyze(ParseTree* tree) { DeduceLiveness(); DeduceMaybeUsed(); + // Remove functions that are not live. + ke::EraseIf(&mod_->functions(), [](ir::Function* fun) -> bool { + return !fun->is_live(); + }); + // This inserts missing return statements at the global scope, so it cannot // be omitted. bool has_public = false; @@ -67,6 +74,8 @@ bool Semantics::Analyze(ParseTree* tree) { return false; } + assert(errors.ok()); + // All heap allocations must be owned by a ParseNode. assert(!pending_heap_allocation_); return true; @@ -173,6 +182,23 @@ bool Semantics::CheckVarDecl(VarDeclBase* decl) { if (!decl->as() && is_const && !decl->init() && !decl->is_public()) report(decl->pos(), 251); + ir::Value* init = nullptr; + if (auto rhs = decl->init_rhs()) { + if ((init = CheckRvalue(rhs)) == nullptr) + return false; + } + + auto def = new ir::Variable(decl, init); + if (fun_) { + assert(sc_->local_vars().find(decl) == sc_->local_vars().end()); + ir_->add(def); + sc_->local_vars().emplace(decl, def); + } else { + assert(global_vars_.find(decl) == global_vars_.end()); + mod_->globals().emplace_back(def); + global_vars_.emplace(decl, def); + } + // CheckArrayDecl works on enum structs too. if (type->isArray() || type->isEnumStruct()) { if (!CheckArrayDeclaration(decl)) @@ -182,19 +208,11 @@ bool Semantics::CheckVarDecl(VarDeclBase* decl) { return true; } - auto init = decl->init(); - - // Since we always create an assignment expression, all type checks will - // be performed by the Analyze(sc) call here. - // - // :TODO: write flag when removing ProcessUses - if (init && !CheckRvalue(init)) - return false; - auto vclass = decl->vclass(); auto init_rhs = decl->init_rhs(); if (init && vclass != sLOCAL) { - if (!init_rhs->EvalConst(nullptr, nullptr)) { + if (!Expr::EvalConst(init_rhs, nullptr, nullptr)) { + assert(false); if (vclass == sARGUMENT && init_rhs->is(ExprKind::SymbolExpr)) return true; report(init_rhs->pos(), 8); @@ -328,7 +346,7 @@ static inline int GetOperToken(int token) { } } -bool Semantics::CheckExpr(Expr* expr) { +ir::Value* Semantics::CheckExpr(Expr* expr) { AutoErrorPos aep(expr->pos()); switch (expr->kind()) { case ExprKind::UnaryExpr: @@ -369,26 +387,15 @@ bool Semantics::CheckExpr(Expr* expr) { return CheckTaggedValueExpr(expr->to()); case ExprKind::SizeofExpr: return CheckSizeofExpr(expr->to()); - case ExprKind::RvalueExpr: - return CheckWrappedExpr(expr, expr->to()->expr()); case ExprKind::NamedArgExpr: - return CheckWrappedExpr(expr, expr->to()->expr); + return CheckExpr(expr->to()->expr()); default: assert(false); report(expr, 420) << (int)expr->kind(); - return false; + return nullptr; } } -bool Semantics::CheckWrappedExpr(Expr* outer, Expr* inner) { - if (!CheckExpr(inner)) - return false; - - outer->val() = inner->val(); - outer->set_lvalue(inner->lvalue()); - return true; -} - CompareOp::CompareOp(const token_pos_t& pos, int token, Expr* expr) : pos(pos), token(token), @@ -397,20 +404,6 @@ CompareOp::CompareOp(const token_pos_t& pos, int token, Expr* expr) { } -bool Expr::EvalConst(cell* value, Type** type) { - if (val_.ident != iCONSTEXPR) { - if (!FoldToConstant()) - return false; - assert(val_.ident == iCONSTEXPR); - } - - if (value) - *value = val_.constval(); - if (type) - *type = val_.type(); - return true; -} - static inline bool HasSideEffects(const PoolArray& exprs) { for (const auto& child : exprs) { if (child->HasSideEffects()) @@ -493,64 +486,42 @@ bool Expr::HasSideEffects() { } } -bool Semantics::CheckScalarType(Expr* expr) { - const auto& val = expr->val(); - if (val.type()->isArray()) { - if (val.sym) - report(expr, 33) << val.sym->name(); - else - report(expr, 29); - return false; - } - if (val.type()->asEnumStruct()) { - report(expr, 447); +bool Semantics::CheckScalarType(Expr* expr, QualType type) { + if (type->isArray() || + type->isEnumStruct() || + type->isReference() || + type->isVoid()) + { + report(expr, 454) << type; return false; } return true; } -Expr* Semantics::AnalyzeForTest(Expr* expr) { - if (!CheckRvalue(expr)) +ir::Value* Semantics::AnalyzeForTest(Expr* expr) { + ir::Value* val = CheckRvalue(expr); + if (!val) return nullptr; - if (!CheckScalarType(expr)) + if (!CheckScalarType(expr, val->type())) return nullptr; - auto& val = expr->val(); - if (!val.type()->isInt() && !val.type()->isBool()) { - UserOperation userop; - if (find_userop(*sc_, '!', val.type(), 0, 1, &val, &userop)) { - // Call user op for '!', then invert it. EmitTest will fold out the - // extra invert. - // - // First convert to rvalue, since user operators should never - // taken an lvalue. - if (expr->lvalue()) - expr = new RvalueExpr(expr); - - expr = new CallUserOpExpr(userop, expr); - expr = new UnaryExpr(expr->pos(), '!', expr); - expr->val().ident = iEXPRESSION; - expr->val().set_type(types_->type_bool()); - return expr; - } + if (!val->type()->isInt() && !val->type()->isBool()) { + if (auto op = MaybeCallUserOp(expr, '!', val, nullptr)) + val = op; } - if (val.ident == iCONSTEXPR) { + if (auto cv = val->as()) { if (!sc_->preprocessing()) { - if (val.constval()) + if (cv->value()) report(expr, 206); else report(expr, 205); } - } else if (auto sym_expr = expr->as()) { - if (sym_expr->decl()->as()) - report(expr, 249); + } else if (auto ref = val->as()) { + report(expr, 249); } - if (expr->lvalue()) - return new RvalueExpr(expr); - - return expr; + return val; } RvalueExpr::RvalueExpr(Expr* expr) @@ -570,116 +541,109 @@ RvalueExpr::RvalueExpr(Expr* expr) } } -void -RvalueExpr::ProcessUses(SemaContext& sc) -{ +void RvalueExpr::ProcessUses(SemaContext& sc) { expr_->MarkAndProcessUses(sc); } -bool Semantics::CheckUnaryExpr(UnaryExpr* unary) { +ir::Value* Semantics::CheckUnaryExpr(UnaryExpr* unary) { AutoErrorPos aep(unary->pos()); - auto expr = unary->expr(); - if (!CheckRvalue(expr)) - return false; - if (!CheckScalarType(expr)) - return false; - - if (expr->lvalue()) - expr = unary->set_expr(new RvalueExpr(expr)); - - auto& out_val = unary->val(); - out_val = expr->val(); + ir::Value* val = CheckRvalue(unary->expr()); + if (!val) + return nullptr; + if (!CheckScalarType(unary, val->type())) + return nullptr; // :TODO: check for invalid types UserOperation userop; switch (unary->token()) { - case '~': - if (out_val.ident == iCONSTEXPR) - out_val.set_constval(~out_val.constval()); - break; - case '!': - if (find_userop(*sc_, '!', out_val.type(), 0, 1, &out_val, &userop)) { - expr = unary->set_expr(new CallUserOpExpr(userop, expr)); - out_val = expr->val(); - unary->set_userop(); - } else if (out_val.ident == iCONSTEXPR) { - out_val.set_constval(!out_val.constval()); + case '~': { + if (val->type()->isFloat()) { + report(unary, 453) << "~" << val->type(); + return nullptr; } - out_val.set_type(types_->type_bool()); - break; - case '-': - if (out_val.ident == iCONSTEXPR && out_val.type()->isFloat()) { - float f = sp::FloatCellUnion(out_val.constval()).f32; - out_val.set_constval(sp::FloatCellUnion(-f).cell); - } else if (find_userop(*sc_, '-', out_val.type(), 0, 1, &out_val, &userop)) { - expr = unary->set_expr(new CallUserOpExpr(userop, expr)); - out_val = expr->val(); - unary->set_userop(); - } else if (out_val.ident == iCONSTEXPR) { - /* the negation of a fixed point number is just an integer negation */ - out_val.set_constval(-out_val.constval()); + + if (auto cv = val->as()) + return new ir::Const(unary, cv->type(), ~cv->value()); + + return new ir::UnaryOp(unary, val->type(), val); + } + case '!': { + auto type = types_->get_bool(); + + if (auto op = MaybeCallUserOp(unary, '!', val, nullptr)) + val = op; + + if (auto cv = val->as()) + return new ir::Const(unary, type, !cv->value()); + + return new ir::UnaryOp(unary, types_->get_bool(), val); + } + case '-': { + if (auto op = MaybeCallUserOp(unary, '~', val, nullptr)) + val = op; + + if (auto cv = val->as()) { + cell_t new_value; + if (cv->type()->isFloat()) { + float f = sp::FloatCellUnion(cv->value()).f32; + new_value = sp::FloatCellUnion(-f).cell; + } else { + new_value = -cv->value(); + } + return new ir::Const(unary, val->type(), new_value); } - break; + return new ir::UnaryOp(unary, val->type(), val); + } default: assert(false); + return nullptr; } - - if (out_val.ident != iCONSTEXPR) - out_val.ident = iEXPRESSION; - return true; } -void -UnaryExpr::ProcessUses(SemaContext& sc) -{ +void UnaryExpr::ProcessUses(SemaContext& sc) { expr_->MarkAndProcessUses(sc); } -bool Semantics::CheckIncDecExpr(IncDecExpr* incdec) { +ir::Value* Semantics::CheckIncDecExpr(IncDecExpr* incdec) { AutoErrorPos aep(incdec->pos()); - auto expr = incdec->expr(); - if (!CheckExpr(expr)) - return false; - if (!CheckScalarType(expr)) - return false; - if (!expr->lvalue()) { + auto val = CheckExpr(incdec->expr()); + if (!val) + return nullptr; + if (!CheckScalarType(incdec, val->type())) + return nullptr; + auto lval = val->as(); + if (!lval) { report(incdec, 22); - return false; + return nullptr; } - const auto& expr_val = expr->val(); - if (expr_val.ident != iACCESSOR) { - if (expr_val.sym && expr_val.sym->is_const()) { - report(incdec, 22); /* assignment to const argument */ - return false; - } - } else { - if (!expr_val.accessor()->setter()) { - report(incdec, 152) << expr_val.accessor()->name(); - return false; + if (lval->type().is_const()) { + report(incdec, 22); + return nullptr; + } + + if (auto prop = lval->as()) { + auto decl = prop->decl(); + if (!decl->setter()) { + report(incdec, 152) << decl->name(); + return nullptr; } - if (!expr_val.accessor()->getter()) { - report(incdec, 149) << expr_val.accessor()->name(); - return false; + if (!decl->getter()) { + report(incdec, 149) << decl->name(); + return nullptr; } - markusage(expr_val.accessor()->getter(), uREAD); - markusage(expr_val.accessor()->setter(), uREAD); + markusage(decl->getter(), uREAD); + markusage(decl->setter(), uREAD); } - Type* type = expr_val.type(); - if (type->isReference()) - type = type->inner(); - - find_userop(*sc_, incdec->token(), type, 0, 1, &expr_val, &incdec->userop()); + ir::Value* getter = BuildRvalue(incdec, lval); + if (auto userop = MaybeCallUserOp(incdec, incdec->token(), getter, nullptr)) + getter = userop; - // :TODO: more type checks - auto& val = incdec->val(); - val.ident = iEXPRESSION; - val.set_type(type); - return true; + return new ir::IncDecOp(incdec, getter->type(), lval, getter); } void @@ -705,180 +669,135 @@ BinaryExpr::BinaryExpr(const token_pos_t& pos, int token, Expr* left, Expr* righ oper_tok_ = GetOperToken(token_); } -bool Semantics::CheckBinaryExpr(BinaryExpr* expr) { - AutoErrorPos aep(expr->pos()); +static inline bool CanConstFoldType(QualType type) { + return type->isInt() || + type->isChar() || + type->isBool() || + type->isEnum(); +} - auto left = expr->left(); - auto right = expr->right(); - if (!CheckExpr(left) || !CheckRvalue(right)) - return false; +ir::Value* Semantics::CheckBinaryExpr(BinaryExpr* expr) { + AutoErrorPos aep(expr->pos()); int token = expr->token(); - if (token != '=') { - if (!CheckScalarType(left)) - return false; - if (!CheckScalarType(right)) - return false; - } - if (IsAssignOp(token)) { - // Mark the left-hand side as written as soon as we can. - if (Decl* sym = left->val().sym) { - markusage(sym, uWRITTEN); - - // If it's an outparam, also mark it as read. - if (sym->vclass() == sARGUMENT && - (sym->type()->isReference() || sym->type()->isArray())) - { - markusage(sym, uREAD); - } - } else if (auto* accessor = left->val().accessor()) { - if (!accessor->setter()) { - report(expr, 152) << accessor->name(); - return false; - } - markusage(accessor->setter(), uREAD); - if (accessor->getter() && token != '=') - markusage(accessor->getter(), uREAD); - } - - if (!CheckAssignmentLHS(expr)) - return false; - if (token != '=' && !CheckRvalue(left->pos(), left->val())) - return false; - } else if (left->lvalue()) { - if (!CheckRvalue(left->pos(), left->val())) - return false; - left = expr->set_left(new RvalueExpr(left)); - } - - // RHS is always loaded. Note we do this after validating the left-hand side, - // so ValidateAssignment has an original view of RHS. - if (right->lvalue()) - right = expr->set_right(new RvalueExpr(right)); - - const auto& left_val = left->val(); - const auto& right_val = right->val(); - - auto oper_tok = expr->oper(); - if (oper_tok) { - assert(token != '='); + auto left = CheckExpr(expr->left()); + if (!left) + return nullptr; + auto right = CheckRvalue(expr->right()); + if (!right) + return nullptr; - if (left_val.type()->isArray()) { - const char* ptr = (left_val.sym != nullptr) ? left_val.sym->name()->chars() : "-unknown-"; - report(expr, 33) << ptr; /* array must be indexed */ - return false; + auto lval = left->as(); + if (IsAssignOp(token)) { + if (!lval) { + report(expr, 455); + return nullptr; } - if (right_val.type()->isArray()) { - const char* ptr = (right_val.sym != nullptr) ? right_val.sym->name()->chars() : "-unknown-"; - report(expr, 33) << ptr; /* array must be indexed */ - return false; + if (!CheckAssignmentLHS(expr, lval)) + return nullptr; + if (token != '=') { + left = BuildRvalue(expr->left(), lval); + MarkUsage(lval, uREAD); } - /* ??? ^^^ should do same kind of error checking with functions */ + MarkUsage(lval, uWRITTEN); + } else if (lval) { + left = BuildRvalue(expr->left(), lval); } - // The assignment operator is overloaded separately. - if (IsAssignOp(token)) { - if (!CheckAssignmentRHS(expr)) - return false; + if (token != '=') { + if (!CheckScalarType(expr->left(), left->type())) + return nullptr; + if (!CheckScalarType(expr->right(), right->type())) + return nullptr; } - auto& val = expr->val(); - val.ident = iEXPRESSION; - val.set_type(left_val.type()); + ir::Value* out = nullptr; + if (token != '=') + out = MaybeCallUserOp(expr, expr->token(), left, right); - auto& assignop = expr->assignop(); - if (assignop.sym) - val.set_type(assignop.sym->type()); + if (!out) { + TypeChecker::DoCoerce(expr->pos(), *left->type(), *right->type(), TypeChecker::Commutative); - if (oper_tok) { - auto& userop = expr->userop(); - if (find_userop(*sc_, oper_tok, left_val.type(), right_val.type(), 2, nullptr, &userop)) { - val.set_type(userop.sym->type()); - } else if (left_val.ident == iCONSTEXPR && right_val.ident == iCONSTEXPR) { - char boolresult = FALSE; - TypeChecker::DoCoerce(expr->pos(), left_val.type(), right_val.type()); - val.ident = iCONSTEXPR; - val.set_constval(calc(left_val.constval(), oper_tok, right_val.constval(), - &boolresult)); - } else { - // For the purposes of tag matching, we consider the order to be irrelevant. - Type* left_type = left_val.type(); - if (left_type->isReference()) - left_type = left_type->inner(); - - Type* right_type = right_val.type(); - if (right_type->isReference()) - right_type = right_type->inner(); - - TypeChecker::DoCoerce(expr->pos(), left_type, right_type, TypeChecker::Commutative); + auto left_cv = left->as(); + auto right_cv = right->as(); + if (left_cv && CanConstFoldType(left_cv->type()) && + right_cv && CanConstFoldType(right_cv->type())) + { + char is_bool = false; + cell result = calc(left_cv->value(), token, right_cv->value(), &is_bool); + auto result_type = is_bool ? types_->get_bool() : left_cv->type(); + out = new ir::Const(expr, result_type, result); } + } + if (!out) { + QualType type; if (IsChainedOp(token) || token == tlEQ || token == tlNE) - val.set_type(types_->type_bool()); + type = types_->get_bool(); + else + type = left->type(); + out = new ir::BinaryOp(expr, type, token, left, right); } - return true; + if (IsAssignOp(token)) + out = new ir::Store(expr, lval->type(), lval, out); + + return out; } -bool Semantics::CheckAssignmentLHS(BinaryExpr* expr) { - auto left = expr->left(); - int left_ident = left->val().ident; - if (left_ident == iARRAYCHAR) { +bool Semantics::CheckAssignmentLHS(BinaryExpr* expr, ir::Lvalue* lval) { + if (lval->type()->isCharArray()) { // This is a special case, assigned to a packed character in a cell // is permitted. return true; } - int oper_tok = expr->oper(); - if (auto left_array = left->val().type()->as()) { - // array assignment is permitted too (with restrictions) - if (oper_tok) { - report(expr, 23); - return false; - } - + // :TODO: is this needed? TypeChecker should cover it. + if (auto left_array = lval->type()->as()) { for (auto iter = left_array; iter; iter = iter->inner()->as()) { if (!iter->size()) { - report(left, 46); + report(expr->left(), 46); return false; } } return true; } - if (!left->lvalue()) { - report(expr, 22); - return false; - } - - const auto& left_val = left->val(); // may not change "constant" parameters - if (!expr->initializer() && left_val.sym && left_val.sym->is_const()) { + if (lval->type().is_const()) { report(expr, 22); return false; } return true; } -bool Semantics::CheckAssignmentRHS(BinaryExpr* expr) { - auto left = expr->left(); - auto right = expr->right(); - const auto& left_val = left->val(); - const auto& right_val = right->val(); +static inline void CheckSelfAssignment(BinaryExpr* expr, ir::Lvalue* lval, ir::Value* rval) { + auto left_var = lval->as(); + if (!left_var) + return; - if (left_val.ident == iVARIABLE) { - const auto& right_val = right->val(); - if (right_val.ident == iVARIABLE && right_val.sym == left_val.sym && !expr->oper()) - report(expr, 226) << left_val.sym->name(); // self-assignment - } + auto load = rval->as(); + if (!load) + return; + + auto right_var = load->lval()->as(); + if (!right_var) + return; + + if (left_var->var() == right_var->var()) + report(expr, 226) << left_var->var()->decl()->name(); +} + +bool Semantics::CheckAssignmentRHS(BinaryExpr* expr, ir::Lvalue* lval, ir::Value* rval) { + CheckSelfAssignment(expr, lval, rval); - if (auto left_array = left_val.type()->as()) { - TypeChecker tc(expr, left_val.type(), right_val.type(), TypeChecker::Assignment); + if (auto left_array = lval->type()->as()) { + TypeChecker tc(expr, lval->type(), rval->type(), TypeChecker::Assignment); if (!tc.Coerce()) return false; - auto right_array = right_val.type()->to(); + auto right_array = rval->type()->to(); if (right_array->inner()->isArray()) { report(expr, 23); return false; @@ -891,25 +810,28 @@ bool Semantics::CheckAssignmentRHS(BinaryExpr* expr) { expr->set_array_copy_length(CalcArraySize(right_array)); } else { - if (right_val.type()->isArray()) { + if (rval->type()->isArray()) { // Hack. Special case array literals assigned to an enum struct, // since we don't have the infrastructure to deduce an RHS type // yet. - if (!left_val.type()->isEnumStruct() || !right->as()) { + if (!lval->type()->isEnumStruct() || !expr->right()->as()) { report(expr, 6); // must be assigned to an array return false; } return true; } - // Userop tag will be propagated by the caller. - find_userop(*sc_, 0, right_val.type(), left_val.type(), 2, &left_val, &expr->assignop()); +#if 0 + // :TODO: assignment operator overload +#endif } +#if 0 if (!expr->oper() && - !checkval_string(&left_val, &right_val) && - !expr->assignop().sym) + !(lval->type()->isCharArray() || rval->type()->isCharArray()) /* + :TODO: !expr->assignop().sym*/) { + // :TODO: needed? if (left_val.type()->isArray() && ((left_val.type()->isChar() && !right_val.type()->isChar()) || (!left_val.type()->isChar() && right_val.type()->isChar()))) @@ -917,8 +839,8 @@ bool Semantics::CheckAssignmentRHS(BinaryExpr* expr) { report(expr, 179) << left_val.type() << right_val.type(); return false; } - if (left_val.type()->asEnumStruct() || right_val.type()->asEnumStruct()) { - if (left_val.type() != right_val.type()) { + if (lval->type()->asEnumStruct() || rval->type()->asEnumStruct()) { + if (lval->type() != rval->type()) { report(expr, 134) << left_val.type() << right_val.type(); return false; } @@ -929,35 +851,49 @@ bool Semantics::CheckAssignmentRHS(BinaryExpr* expr) { TypeChecker::DoCoerce(expr->pos(), left_val.type(), right_val.type()); } } +#endif return true; } -static inline bool -IsTypeBinaryConstantFoldable(Type* type) -{ +static inline bool IsTypeBinaryConstantFoldable(QualType type) { if (type->isEnum() || type->isInt()) return true; return false; } -bool -BinaryExpr::FoldToConstant() -{ +bool Expr::EvalConst(Expr* expr, cell* value, QualType* type) { + if (auto tve = expr->as()) { + *value = tve->value(); + *type = QualType(tve->type()); + return true; + } + if (auto bin = expr->as()) + return bin->ConstantFold(value, type); + return false; +} + +bool BinaryExpr::ConstantFold(cell* value, QualType* type) { cell left_val, right_val; - Type* left_type; - Type* right_type; + QualType left_type, right_type; - if (!left_->EvalConst(&left_val, &left_type) || !right_->EvalConst(&right_val, &right_type)) - return false; - if (IsAssignOp(token_) || userop_.sym) + if (!Expr::EvalConst(left_, &left_val, &left_type) || + !Expr::EvalConst(right_, &right_val, &right_type)) + { return false; + } + // If we went through sema we could drop this... if (!IsTypeBinaryConstantFoldable(left_type) || !IsTypeBinaryConstantFoldable(right_type)) return false; + if (left_type != right_type) + return false; + + *type = left_type; + switch (token_) { case '*': - val_.set_constval(left_val * right_val); + *value = left_val * right_val; break; case '/': case '%': @@ -970,33 +906,33 @@ BinaryExpr::FoldToConstant() return false; } if (token_ == '/') - val_.set_constval(left_val / right_val); + *value = left_val / right_val; else - val_.set_constval(left_val % right_val); + *value = left_val % right_val; break; case '+': - val_.set_constval(left_val + right_val); + *value = left_val + right_val; break; case '-': - val_.set_constval(left_val - right_val); + *value = left_val - right_val; break; case tSHL: - val_.set_constval(left_val << right_val); + *value = left_val << right_val; break; case tSHR: - val_.set_constval(left_val >> right_val); + *value = left_val >> right_val; break; case tSHRU: - val_.set_constval(uint32_t(left_val) >> uint32_t(right_val)); + *value = uint32_t(left_val) >> uint32_t(right_val); break; case '&': - val_.set_constval(left_val & right_val); + *value = left_val & right_val; break; case '^': - val_.set_constval(left_val ^ right_val); + *value = left_val ^ right_val; break; case '|': - val_.set_constval(left_val | right_val); + *value = left_val | right_val; break; default: return false; @@ -1004,125 +940,64 @@ BinaryExpr::FoldToConstant() return true; } -bool Semantics::CheckLogicalExpr(LogicalExpr* expr) { +ir::Value* Semantics::CheckLogicalExpr(LogicalExpr* expr) { AutoErrorPos aep(expr->pos()); - auto left = expr->left(); - auto right = expr->right(); - - if ((left = AnalyzeForTest(left)) == nullptr) - return false; - if ((right = AnalyzeForTest(right)) == nullptr) - return false; - - if (left->lvalue()) - left = new RvalueExpr(left); - if (right->lvalue()) - right = new RvalueExpr(right); + auto left = AnalyzeForTest(expr->left()); + if (!left) + return nullptr; + auto right = AnalyzeForTest(expr->right()); + if (!right) + return nullptr; - expr->set_left(left); - expr->set_right(right); + auto bool_type = types_->get_bool(); - const auto& left_val = left->val(); - const auto& right_val = right->val(); - auto& val = expr->val(); - if (left_val.ident == iCONSTEXPR && right_val.ident == iCONSTEXPR) { - val.ident = iCONSTEXPR; + auto left_cv = left->as(); + auto right_cv = right->as(); + if (left_cv && right_cv) { if (expr->token() == tlOR) - val.set_constval((left_val.constval() || right_val.constval())); - else if (expr->token() == tlAND) - val.set_constval((left_val.constval() && right_val.constval())); - else - assert(false); - } else { - val.ident = iEXPRESSION; + return new ir::Const(expr, bool_type, left_cv->value() || right_cv->value()); + if (expr->token() == tlAND) + return new ir::Const(expr, bool_type, left_cv->value() && right_cv->value()); + assert(false); } - val.sym = nullptr; - val.set_type(types_->type_bool()); - return true; + return new ir::BinaryOp(expr, bool_type, expr->token(), left, right); } -bool Semantics::CheckChainedCompareExpr(ChainedCompareExpr* chain) { - auto first = chain->first(); - if (!CheckRvalue(first)) - return false; - if (first->lvalue()) - first = chain->set_first(new RvalueExpr(first)); - - for (auto& op : chain->ops()) { - if (!CheckRvalue(op.expr)) - return false; - if (op.expr->lvalue()) - op.expr = new RvalueExpr(op.expr); - } - - Expr* left = first; - bool all_const = (left->val().ident == iCONSTEXPR); - bool constval = true; - - auto& val = chain->val(); - val.ident = iEXPRESSION; - val.set_type(types_->type_bool()); +ir::Value* Semantics::CheckChainedCompareExpr(ChainedCompareExpr* chain) { + auto first = CheckRvalue(chain->first()); + if (!first) + return nullptr; - for (auto& op : chain->ops()) { - Expr* right = op.expr; - const auto& left_val = left->val(); - const auto& right_val = right->val(); + ir::Value* left = first; + ir::Value* out = nullptr; + for (const auto& chain_op : chain->ops()) { + auto right = CheckRvalue(chain_op.expr); + if (!right) + return nullptr; - if (left_val.type()->isArray()) { - const char* ptr = (left_val.sym != nullptr) ? left_val.sym->name()->chars() : "-unknown-"; - report(left, 33) << ptr; /* array must be indexed */ - return false; - } - if (right_val.type()->isArray()) { - const char* ptr = (right_val.sym != nullptr) ? right_val.sym->name()->chars() : "-unknown-"; - report(right, 33) << ptr; /* array must be indexed */ - return false; - } + assert(!right->HasSideEffects()); - if (find_userop(*sc_, op.oper_tok, left_val.type(), right_val.type(), 2, nullptr, - &op.userop)) - { - if (!op.userop.sym->type()->isBool()) { - report(op.pos, 51) << get_token_string(op.token); - return false; + auto op = MaybeCallUserOp(chain, chain_op.token, left, right); + if (op) { + if (!op->type()->isBool()) { + report(chain_op.pos, 51) << get_token_string(chain_op.token); + return nullptr; } } else { - // For the purposes of tag matching, we consider the order to be irrelevant. - if (!checkval_string(&left_val, &right_val)) - matchtag_commutative(left_val.type(), right_val.type(), MATCHTAG_DEDUCE); - } - - if (right_val.ident != iCONSTEXPR || op.userop.sym) - all_const = false; - - // Fold constants as we go. - if (all_const) { - switch (op.token) { - case tlLE: - constval &= left_val.constval() <= right_val.constval(); - break; - case tlGE: - constval &= left_val.constval() >= right_val.constval(); - break; - case '>': - constval &= left_val.constval() > right_val.constval(); - break; - case '<': - constval &= left_val.constval() < right_val.constval(); - break; - default: - assert(false); - break; - } + // :TODO: type check + // :TODO: Compare struct should be Expr + op = new ir::BinaryOp(chain, types_->get_bool(), chain_op.token, left, right); } + if (!out) + out = op; + else + out = new ir::BinaryOp(chain, types_->get_bool(), tlAND, out, op); left = right; } - if (all_const) - val.set_constval(constval ? 1 : 0); - return true; + return out; } void @@ -1133,61 +1008,36 @@ ChainedCompareExpr::ProcessUses(SemaContext& sc) op.expr->MarkAndProcessUses(sc); } -bool Semantics::CheckTernaryExpr(TernaryExpr* expr) { +ir::Value* Semantics::CheckTernaryExpr(TernaryExpr* expr) { AutoErrorPos aep(expr->pos()); - auto first = expr->first(); - auto second = expr->second(); - auto third = expr->third(); - - if (!CheckRvalue(first) || !CheckRvalue(second) || !CheckRvalue(third)) - return false; - - if (first->lvalue()) { - first = expr->set_first(new RvalueExpr(first)); - } else if (first->val().ident == iCONSTEXPR) { - report(first, first->val().constval() ? 206 : 205); - } - - if (second->lvalue()) - second = expr->set_second(new RvalueExpr(second)); - if (third->lvalue()) - third = expr->set_third(new RvalueExpr(third)); + auto first = CheckRvalue(expr->first()); + if (!first) + return nullptr; + auto second = CheckRvalue(expr->second()); + if (!second) + return nullptr; + auto third = CheckRvalue(expr->third()); + if (!third) + return nullptr; - const auto& left = second->val(); - const auto& right = third->val(); + if (auto cv = first->as()) + report(expr->first(), cv->value() ? 206 : 205); - TypeChecker tc(second, left.type(), right.type(), TypeChecker::Generic, + TypeChecker tc(expr->second(), second->type(), third->type(), TypeChecker::Generic, TypeChecker::Ternary | TypeChecker::Commutative); if (!tc.Check()) - return false; + return nullptr; // Huge hack: for now, take the larger of two char arrays. - auto& val = expr->val(); - val = left; - if (val.type()->isCharArray() && right.type()->isCharArray()) { - auto left_array = val.type()->to(); - auto right_array = right.type()->to(); - if (right_array->size() > left_array->size()) - val = right; - } - - val.ident = iEXPRESSION; - return true; -} - -bool -TernaryExpr::FoldToConstant() -{ - cell cond, left, right; - if (!first_->EvalConst(&cond, nullptr) || second_->EvalConst(&left, nullptr) || - !third_->EvalConst(&right, nullptr)) - { - return false; + auto type = second->type(); + if (second->type()->isCharArray() && third->type()->isCharArray()) { + auto second_array = second->type()->to(); + auto third_array = third->type()->to(); + if (third_array->size() > second_array->size()) + type = third->type(); } - - val_.set_constval(cond ? left : right); - return true; + return new ir::TernaryOp(expr, type, first, second, third); } void @@ -1206,26 +1056,21 @@ TernaryExpr::ProcessDiscardUses(SemaContext& sc) third_->ProcessUses(sc); } -bool Semantics::CheckCastExpr(CastExpr* expr) { +ir::Value* Semantics::CheckCastExpr(CastExpr* expr) { AutoErrorPos aep(expr->pos()); - Type* atype = expr->type(); - if (atype->isVoid()) { + QualType to_type = QualType(expr->type()); + if (to_type->isVoid()) { report(expr, 144); - return false; + return nullptr; } - if (!CheckExpr(expr->expr())) - return false; - - auto& out_val = expr->val(); - - out_val = expr->expr()->val(); - expr->set_lvalue(expr->expr()->lvalue()); - - Type* ltype = out_val.type(); + ir::Value* val = CheckExpr(expr->expr()); + if (!val) + return nullptr; - auto actual_array = ltype->as(); + QualType from_type = val->type(); + auto actual_array = from_type->as(); if (actual_array) { // Unwind back to the inner. auto iter = actual_array; @@ -1234,32 +1079,39 @@ bool Semantics::CheckCastExpr(CastExpr* expr) { break; iter = iter->inner()->to(); } - ltype = iter->inner(); + from_type = QualType(iter->inner()); } - if (ltype->isObject() || atype->isObject()) { - TypeChecker::DoCoerce(expr->pos(), atype, out_val.type()); - } else if (ltype->isFunction() != atype->isFunction()) { + assert(!val->as()); + + if (from_type->isObject() || to_type->isObject()) { + //TypeChecker::DoCoerce(expr->pos(), to_type, from_type); + assert(false); + } else if (from_type->isFunction() != to_type->isFunction()) { // Warn: unsupported cast. report(expr, 237); - } else if (ltype->isFunction() && atype->isFunction()) { - TypeChecker::DoCoerce(expr->pos(), atype, out_val.type()); - } else if (out_val.type()->isVoid()) { + } else if (from_type->isFunction() && to_type->isFunction()) { + //TypeChecker::DoCoerce(expr->pos(), to_type, from_type); + assert(false); + } else if (from_type->isVoid()) { report(expr, 89); - } else if (atype->isEnumStruct() || ltype->isEnumStruct()) { - report(expr, 95) << atype; + return nullptr; + } else if (to_type->isEnumStruct() || from_type->isEnumStruct()) { + report(expr, 95) << to_type; + return nullptr; } - if (ltype->isReference() && !atype->isReference()) { - if (atype->isEnumStruct()) { + if (from_type->isReference() && !to_type->isReference()) { + if (to_type->isEnumStruct()) { report(expr, 136); - return false; + return nullptr; } - atype = types_->defineReference(atype); + to_type = QualType(types_->defineReference(*to_type)); } - if (actual_array) - atype = types_->redefineArray(atype, actual_array); - out_val.set_type(atype); - return true; + assert(!actual_array); + //if (actual_array) + // to_type = types_->redefineArray(to_type, actual_array); + + return val; } void @@ -1276,48 +1128,43 @@ void SymbolExpr::MarkUsed(SemaContext& sc) { // checks, so for now, we forbid it by default. Since the '.' operator *is* // prepared for this, we have a special analysis option to allow returning // types as values. -bool Semantics::CheckSymbolExpr(SymbolExpr* expr, bool allow_types) { +ir::Value* Semantics::CheckSymbolExpr(SymbolExpr* expr, bool allow_types) { AutoErrorPos aep(expr->pos()); auto decl = expr->decl(); if (!decl) { // This can happen if CheckSymbolExpr is called during name resolution. assert(cc_.reports()->total_errors() > 0); - return false; + return nullptr; } - auto& val = expr->val(); - val.ident = decl->ident(); - val.sym = decl; - // Don't expose the tag of old enumroots. Type* type = decl->type(); if (decl->as() && !type->asEnumStruct() && decl->ident() == iCONSTEXPR) { report(expr, 174) << decl->name(); - return false; + return nullptr; } - val.set_type(type); if (auto fun = decl->as()) { fun = fun->canonical(); if (fun->is_native()) { report(expr, 76); - return false; + return nullptr; } if (fun->return_array()) { report(expr, 182); - return false; + return nullptr; } if (!fun->impl()) { report(expr, 4) << fun->name(); - return false; + return nullptr; } funcenum_t* fe = funcenum_for_symbol(cc_, fun); - // New-style "closure". - val.ident = iEXPRESSION; - val.set_type(fe->type); + // New-style "closure". TODO: when we get rid of funcenum_t, this won't + // be necessary. + type = fe->type; // Mark as being indirectly invoked. Direct invocations go through // BindCallTarget. @@ -1325,40 +1172,54 @@ bool Semantics::CheckSymbolExpr(SymbolExpr* expr, bool allow_types) { } switch (decl->ident()) { - case iVARIABLE: - expr->set_lvalue(true); - break; - case iFUNCTN: - // Not an l-value. - break; + case iVARIABLE: { + auto var = decl->as(); + assert(var); + + auto it = sc_->local_vars().find(var); + assert(it != sc_->local_vars().end()); + + return new ir::VariableRef(expr, QualType(type), it->second); + } + case iFUNCTN: { + auto fun = BuildFunction(decl->to()); + return BuildFunctionRef(expr, fun); + } case iTYPENAME: if (!allow_types) { report(expr, 174) << decl->name(); - return false; + return nullptr; } - break; + return new ir::TypeRef(expr, QualType(type)); case iCONSTEXPR: - val.set_constval(decl->ConstVal()); - break; + return new ir::Const(expr, QualType(type), decl->ConstVal()); default: // Should not be a symbol. assert(false); + return nullptr; } - return true; } -bool Semantics::CheckCommaExpr(CommaExpr* comma) { +ir::Value* Semantics::CheckCommaExpr(CommaExpr* comma) { AutoErrorPos aep(comma->pos()); - for (const auto& expr : comma->exprs()) { - if (!CheckRvalue(expr)) - return false; - } + // A single value acts as a passthrough. + if (comma->exprs().size() == 1) + return CheckExpr(comma->exprs()[0]); - Expr* last = comma->exprs().back(); - if (comma->exprs().size() > 1 && last->lvalue()) { - last = new RvalueExpr(last); - comma->exprs().back() = last; + std::vector values; + for (size_t i = 0; i < comma->exprs().size(); i++) { + auto expr = comma->exprs().at(i); + + // Don't bother converting ignored results to rvalues. + ir::Value* val; + if (i == comma->exprs().size() - 1) + val = CheckRvalue(expr); + else + val = CheckExpr(expr); + if (!val) + return nullptr; + values.emplace_back(val); } for (size_t i = 0; i < comma->exprs().size() - 1; i++) { @@ -1367,14 +1228,7 @@ bool Semantics::CheckCommaExpr(CommaExpr* comma) { report(expr, 231) << i; } - comma->val() = last->val(); - comma->set_lvalue(last->lvalue()); - - // Don't propagate a constant if it would cause Emit() to shortcut and not - // emit other expressions. - if (comma->exprs().size() > 1 && comma->val().ident == iCONSTEXPR) - comma->val().ident = iEXPRESSION; - return true; + return new ir::CommaOp(comma, values.back()->type(), values); } void @@ -1392,108 +1246,73 @@ CommaExpr::ProcessDiscardUses(SemaContext& sc) expr->ProcessUses(sc); } -bool Semantics::CheckArrayExpr(ArrayExpr* array) { +ir::Value* Semantics::CheckArrayExpr(ArrayExpr* array) { AutoErrorPos aep(array->pos()); - Type* last_type = nullptr; + std::vector values; + + QualType last_type; for (const auto& expr : array->exprs()) { - if (!CheckExpr(expr)) - return false; + auto val = CheckRvalue(expr); + if (!val) + return nullptr; - const auto& val = expr->val(); - if (val.ident != iCONSTEXPR) { + values.emplace_back(val); + + auto cv = val->as(); + if (!cv) { report(expr, 8); - return false; + return nullptr; } if (!last_type) { - last_type = val.type(); + last_type = val->type(); continue; } - TypeChecker tc(array, last_type, val.type(), TypeChecker::Generic); + TypeChecker tc(array, last_type, val->type(), TypeChecker::Generic); if (!tc.Check()) - return false; + return nullptr; } - auto& val = array->val(); - val.ident = iEXPRESSION; - val.set_type(types_->defineArray(last_type, (int)array->exprs().size())); - return true; + auto type = types_->defineArray(last_type.ptr(), (int)values.size()); + return new ir::Array(array, QualType(type), values); } -bool Semantics::CheckIndexExpr(IndexExpr* expr) { +ir::Value* Semantics::CheckIndexExpr(IndexExpr* expr) { AutoErrorPos aep(expr->pos()); - auto base = expr->base(); - auto index = expr->index(); - if (!CheckRvalue(base)) - return false; - if (base->lvalue() && base->val().ident == iACCESSOR) - base = expr->set_base(new RvalueExpr(base)); + auto base = CheckRvalue(expr->base()); + if (!base) + return nullptr; - const auto& base_val = base->val(); - if (!base_val.type()->isArray()) { - report(index, 28); - return false; + ArrayType* array = base->type()->as(); + if (!array) { + report(expr, 28); + return nullptr; } - ArrayType* array = base_val.type()->to(); - - if (index) { - if (!CheckRvalue(index)) - return false; - if (!CheckScalarType(index)) - return false; - if (index->lvalue()) - index = expr->set_index(new RvalueExpr(index)); + auto index = CheckRvalue(expr->index()); + if (!index) + return nullptr; - auto idx_type = index->val().type(); - if (!IsValidIndexType(idx_type)) { - report(index, 77) << idx_type; - return false; - } + if (!CheckScalarType(expr, index->type())) + return nullptr; - const auto& index_val = index->val(); - if (index_val.ident == iCONSTEXPR) { - if (!array->isCharArray()) { - /* normal array index */ - if (index_val.constval() < 0 || - (array->size() != 0 && array->size() <= index_val.constval())) - { - report(index, 32) << base_val.sym->name(); /* array index out of bounds */ - return false; - } - } else { - /* character index */ - if (index_val.constval() < 0 || - (array->size() != 0 && array->size() <= index_val.constval())) - { - report(index, 32) << base_val.sym->name(); /* array index out of bounds */ - return false; - } - } - } + auto idx_type = index->type(); + if (!IsValidIndexType(idx_type.ptr())) { + report(expr->index(), 77) << idx_type; + return nullptr; } - auto& out_val = expr->val(); - out_val = base_val; - - if (array->inner()->isArray()) { - // Note: Intermediate arrays are not l-values. - out_val.ident = iEXPRESSION; - out_val.set_type(array->inner()); - return true; + if (auto cv = index->as()) { + auto val = cv->value(); + if (val < 0 || (array->size() != 0 && array->size() <= val)) { + report(expr->index(), 32); + return nullptr; + } } - /* set type to fetch... INDIRECTLY */ - if (array->isCharArray()) - out_val.set_slice(iARRAYCHAR, base_val.sym); - else - out_val.set_slice(iARRAYCELL, base_val.sym); - out_val.set_type(array->inner()); - - expr->set_lvalue(true); - return true; + return new ir::IndexOp(expr, QualType(array->inner()), base, index); } void @@ -1503,133 +1322,101 @@ IndexExpr::ProcessUses(SemaContext& sc) expr_->MarkAndProcessUses(sc); } -bool Semantics::CheckThisExpr(ThisExpr* expr) { - auto sym = expr->decl(); - assert(sym->ident() == iVARIABLE); - - auto& val = expr->val(); - val.ident = sym->ident(); - val.sym = sym; - val.set_type(sym->type()); - expr->set_lvalue(true); - return true; +ir::Value* Semantics::CheckThisExpr(ThisExpr* expr) { + return new ir::ThisRef(expr, QualType(expr->decl()->type())); } -bool Semantics::CheckNullExpr(NullExpr* expr) { - auto& val = expr->val(); - val.set_constval(0); - val.set_type(types_->type_null()); - return true; +ir::Value* Semantics::CheckNullExpr(NullExpr* expr) { + return new ir::Const(expr, types_->get_null(), 0); } -bool Semantics::CheckTaggedValueExpr(TaggedValueExpr* expr) { - auto& val = expr->val(); - val.set_type(expr->type()); - val.set_constval(expr->value()); - return true; +ir::Value* Semantics::CheckTaggedValueExpr(TaggedValueExpr* expr) { + return new ir::Const(expr, QualType(expr->type()), expr->value()); } -bool Semantics::CheckStringExpr(StringExpr* expr) { - auto& val = expr->val(); - val.ident = iEXPRESSION; - val.set_type(types_->defineArray(types_->type_char(), (cell)expr->text()->length() + 1)); - return true; +ir::Value* Semantics::CheckStringExpr(StringExpr* expr) { + auto type = types_->defineArray(types_->type_char(), (cell)expr->text()->length() + 1); + return new ir::CharArrayLiteral(expr, QualType(type)); } -bool Semantics::CheckFieldAccessExpr(FieldAccessExpr* expr, bool from_call) { +ir::Value* Semantics::CheckFieldAccessExpr(FieldAccessExpr* expr, bool from_call) { AutoErrorPos aep(expr->pos()); - auto base = expr->base(); - if (auto sym_expr = base->as()) { - if (!CheckSymbolExpr(sym_expr, true)) - return false; - } else { - if (!CheckRvalue(base)) - return false; - } + ir::Value* base = nullptr; + if (auto sym_expr = expr->base()->as()) + base = CheckSymbolExpr(sym_expr, true); + else + base = CheckRvalue(expr->base()); + if (!base) + return nullptr; int token = expr->token(); if (token == tDBLCOLON) return CheckStaticFieldAccessExpr(expr); - const auto& base_val = base->val(); - switch (base_val.ident) { - case iFUNCTN: - report(expr, 107); - return false; - default: - if (base_val.type()->isArray()) { - report(expr, 96) << expr->name() << "type" << "array"; - return false; - } - break; + if (base->type()->isFunction()) { + report(expr, 107); + return nullptr; + } + if (base->type()->isArray()) { + report(expr, 96) << expr->name() << "type" << "array"; + return nullptr; } - auto& val = expr->val(); - if (base_val.ident == iTYPENAME) { - auto map = MethodmapDecl::LookupMethodmap(base_val.sym); + if (auto type_ref = base->as()) { + auto map = type_ref->type()->asMethodmap(); auto member = map ? map->FindMember(expr->name()) : nullptr; if (!member || !member->as()) { - report(expr, 444) << base_val.sym->name() << expr->name(); - return false; + report(expr, 444) << type_ref->type().ptr() << expr->name(); + return nullptr; } auto method = member->as(); if (!method->is_static()) { report(expr, 176) << method->decl_name() << map->name(); - return false; + return nullptr; } - expr->set_resolved(method); - val.ident = iFUNCTN; - val.sym = method; - markusage(method, uREAD); - return true; + + auto fun = BuildFunction(method); + return BuildFunctionRef(expr, fun); } - Type* base_type = base_val.type(); + QualType base_type = base->type(); if (auto es = base_type->asEnumStruct()) - return CheckEnumStructFieldAccessExpr(expr, base_type, es, from_call); + return CheckEnumStructFieldAccessExpr(expr, base, es, from_call); if (base_type->isReference()) - base_type = base_type->inner(); + base_type = QualType(base_type->inner()); auto map = base_type->asMethodmap(); if (!map) { - report(expr, 104) << base_val.type(); - return false; + report(expr, 104) << base_type; + return nullptr; } auto member = map->FindMember(expr->name()); if (!member) { report(expr, 105) << map->name() << expr->name(); - return false; + return nullptr; } - if (auto prop = member->as()) { - // This is the only scenario in which we need to compute a load of the - // base address. Otherwise, we're only accessing the type. - if (base->lvalue()) - base = expr->set_base(new RvalueExpr(base)); - val.set_type(prop->property_type()); - val.set_accessor(prop); - expr->set_lvalue(true); - return true; - } + if (auto prop = member->as()) + return new ir::PropertyRef(expr, QualType(prop->property_type()), base, prop); auto method = member->as(); if (method->is_static()) { report(expr, 177) << method->decl_name() << map->name() << method->decl_name(); - return false; + return nullptr; } expr->set_resolved(method); if (!from_call) { report(expr, 50); - return false; + return nullptr; } - val.ident = iFUNCTN; - val.sym = method; - markusage(method, uREAD); - return true; + auto fun = BuildFunction(method); + + // :TODO: real fun type + return new ir::FunctionRef(expr, QualType(method->return_type()), fun); } void @@ -1638,7 +1425,7 @@ FieldAccessExpr::ProcessUses(SemaContext& sc) base_->MarkAndProcessUses(sc); } -FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { +ir::Function* Semantics::BindCallTarget(CallExpr* call, Expr* target) { AutoErrorPos aep(target->pos()); switch (target->kind()) { @@ -1673,7 +1460,7 @@ FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { base = expr->set_base(new RvalueExpr(base)); if (resolved->as() || !method->is_static()) call->set_implicit_this(base); - return val.sym->as()->canonical(); + return BuildFunction(val.sym->as()); } case ExprKind::SymbolExpr: { call->set_implicit_this(nullptr); @@ -1691,7 +1478,7 @@ FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { report(target, 170) << decl->name(); return nullptr; } - return mm->ctor(); + return BuildFunction(mm->ctor()); } auto fun = decl->as(); if (!fun) { @@ -1704,7 +1491,7 @@ FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { report(target, 4) << decl->name(); return nullptr; } - return fun; + return BuildFunction(fun); } default: report(target, 12); @@ -1712,7 +1499,7 @@ FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { } } -FunctionDecl* Semantics::BindNewTarget(Expr* target) { +ir::Function* Semantics::BindNewTarget(Expr* target) { AutoErrorPos aep(target->pos()); switch (target->kind()) { @@ -1734,155 +1521,124 @@ FunctionDecl* Semantics::BindNewTarget(Expr* target) { report(expr, 172) << mm->name(); return nullptr; } - return mm->ctor(); + return BuildFunction(mm->ctor()); } } return nullptr; } -bool Semantics::CheckEnumStructFieldAccessExpr(FieldAccessExpr* expr, Type* type, EnumStructDecl* root, - bool from_call) +ir::Value* Semantics::CheckEnumStructFieldAccessExpr(FieldAccessExpr* expr, ir::Value* base, + EnumStructDecl* root, bool from_call) { - expr->set_resolved(FindEnumStructField(type, expr->name())); + auto type = base->type(); + + expr->set_resolved(FindEnumStructField(*type, expr->name())); auto field_decl = expr->resolved(); if (!field_decl) { report(expr, 105) << type << expr->name(); - return false; + return nullptr; } - auto& val = expr->val(); if (auto fun = field_decl->as()) { if (!from_call) { report(expr, 76); - return false; + return nullptr; } - val.ident = iFUNCTN; - val.sym = fun; - markusage(val.sym, uREAD); - return true; + auto ir_fun = BuildFunction(fun); + + // :TODO: real type + return new ir::FunctionRef(expr, QualType(fun->return_type()), ir_fun); } auto field = field_decl->as(); assert(field); - Type* field_type = field->type_info().type; - - val.set_type(field_type); - if (field_type->isArray()) { - // Already an r-value. - val.ident = iEXPRESSION; - } else { - // Need LOAD_I to convert to r-value. - val.ident = iARRAYCELL; - expr->set_lvalue(true); - } - return true; + QualType field_type = QualType(field->type_info().type); + return new ir::FieldRef(expr, field_type, base, field); } -bool Semantics::CheckStaticFieldAccessExpr(FieldAccessExpr* expr) { +ir::Value* Semantics::CheckStaticFieldAccessExpr(FieldAccessExpr* expr) { AutoErrorPos aep(expr->pos()); auto base = expr->base(); const auto& base_val = base->val(); if (base_val.ident != iTYPENAME) { report(expr, 108); - return false; + return nullptr; } Type* type = base_val.type(); Decl* field = FindEnumStructField(type, expr->name()); if (!field) { report(expr, 105) << type << expr->name(); - return false; + return nullptr; } auto fd = field->as(); if (!fd) { report(expr, 445) << field->name(); - return false; + return nullptr; } expr->set_resolved(field); - auto& val = expr->val(); - val.set_constval(fd->offset()); - val.set_type(types_->type_int()); - return true; + return new ir::Const(expr, types_->get_int(), fd->offset()); } -bool Semantics::CheckSizeofExpr(SizeofExpr* expr) { +ir::Value* Semantics::CheckSizeofExpr(SizeofExpr* expr) { AutoErrorPos aep(expr->pos()); Expr* child = expr->child(); - if (auto sym = child->as()) { - if (!CheckSymbolExpr(sym, true)) - return false; - } else { - if (!CheckExpr(child)) - return false; + ir::Value* val = nullptr; + if (auto sym = child->as()) + val = CheckSymbolExpr(sym, true); + else + val = CheckExpr(child); + if (!val) + return nullptr; + + if (auto type_ref = val->as()) { + auto es = type_ref->type()->asEnumStruct(); + if (!es) { + report(child, 72); + return nullptr; + } + return new ir::Const(expr, types_->get_int(), es->array_size()); } - auto& val = expr->val(); - val.set_type(types_->type_int()); - - const auto& cv = child->val(); - switch (cv.ident) { - case iARRAYCELL: - case iVARIABLE: - case iEXPRESSION: - if (auto es = cv.type()->asEnumStruct()) { - val.set_constval(es->array_size()); - } else if (auto array = cv.type()->as()) { - if (!array->size()) { - report(child, 163); - return false; - } - val.set_constval(array->size()); - } else if (cv.ident == iEXPRESSION) { + auto type = val->type(); + if (type->isReference()) + type = QualType(type->inner()); + + switch (type->kind()) { + case TypeKind::Builtin: + case TypeKind::Methodmap: + case TypeKind::Function: + case TypeKind::Object: + case TypeKind::FunctionSignature: + if (type->isVoid()) { report(child, 72); - return false; - } else { - val.set_constval(1); - report(expr, 252); + return nullptr; } - return true; - - case iARRAYCHAR: - report(expr, 252); - val.set_constval(1); - return true; + return new ir::Const(child, types_->get_int(), 1); - case iTYPENAME: { - auto es = cv.sym->as(); - if (!es) { - report(child, 72); - return false; - } - val.set_constval(es->array_size()); - return true; - } + case TypeKind::EnumStruct: + return new ir::Const(child, types_->get_int(), type->asEnumStruct()->array_size()); - case iCONSTEXPR: { - auto access = child->as(); - if (!access || access->token() != tDBLCOLON) { - report(child, 72); - return false; + case TypeKind::Array: { + auto array = type->as(); + if (!array->size()) { + report(child, 163); + return nullptr; } - auto field = access->resolved()->as(); - if (auto array = field->type()->as()) - val.set_constval(array->size()); - else if (auto es = field->type()->asEnumStruct()) - val.set_constval(es->array_size()); - else - val.set_constval(1); - return true; + return new ir::Const(child, types_->get_int(), array->size()); } default: report(child, 72); - return false; + return nullptr; } } @@ -1909,57 +1665,54 @@ DefaultArgExpr::DefaultArgExpr(const token_pos_t& pos, ArgDecl* arg) // accurately construct it. } -bool Semantics::CheckCallExpr(CallExpr* call) { +ir::Value* Semantics::CheckCallExpr(CallExpr* call) { AutoErrorPos aep(call->pos()); // Note: we do not Analyze the call target. We leave this to the // implementation of BindCallTarget. - FunctionDecl* fun; + ir::Function* fun; if (call->token() == tNEW) fun = BindNewTarget(call->target()); else fun = BindCallTarget(call, call->target()); if (!fun) - return false; - - assert(fun->canonical() == fun); - - call->set_fun(fun); + return nullptr; - if (fun->return_type()->isArray() || fun->return_type()->isEnumStruct()) { + FunctionDecl* decl = fun->decl(); + if (decl->return_type()->isArray() || decl->return_type()->isEnumStruct()) { // We need to know the size of the returned array. Recursively analyze // the function. - if (fun->is_analyzing() || !CheckFunctionDecl(fun)) { + if (decl->is_analyzing() || !CheckFunctionDecl(decl)) { report(call, 411); - return false; + return nullptr; } } - markusage(fun, uREAD); + fun_->AddReferenceTo(fun); - auto& val = call->val(); - val.ident = iEXPRESSION; - val.set_type(fun->return_type()); + assert(!decl->return_array()); +#if 0 if (fun->return_array()) NeedsHeapAlloc(call); +#endif // We don't have canonical decls yet, so get the one attached to the symbol. - if (fun->deprecate()) - report(call, 234) << fun->name() << fun->deprecate(); + if (decl->deprecate()) + report(call, 234) << decl->name() << decl->deprecate(); ParamState ps; unsigned int nargs = 0; unsigned int argidx = 0; - auto& arglist = fun->args(); + auto& arglist = decl->args(); if (call->implicit_this()) { if (arglist.empty()) { report(call->implicit_this(), 92); - return false; + return nullptr; } - Expr* param = CheckArgument(call, arglist[0], call->implicit_this(), &ps, 0); + ir::Value* param = CheckArgument(call, arglist[0], call->implicit_this(), &ps, 0); if (!param) - return false; + return nullptr; ps.argv[0] = param; nargs++; argidx++; @@ -1969,9 +1722,9 @@ bool Semantics::CheckCallExpr(CallExpr* call) { for (const auto& param : call->args()) { unsigned int argpos; if (auto named = param->as()) { - int pos = fun->FindNamedArg(named->name); + int pos = decl->FindNamedArg(named->name()); if (pos < 0) { - report(call, 17) << named->name; + report(call, 17) << named->name(); break; } argpos = pos; @@ -1979,28 +1732,28 @@ bool Semantics::CheckCallExpr(CallExpr* call) { } else { if (namedparams) { report(call, 44); // positional parameters must precede named parameters - return false; + return nullptr; } argpos = nargs; if (argidx >= arglist.size()) { report(param->pos(), 92); - return false; + return nullptr; } } if (argpos >= SP_MAX_CALL_ARGUMENTS) { report(call, 45); // too many function arguments - return false; + return nullptr; } if (argpos < ps.argv.size() && ps.argv[argpos]) { report(call, 58); // argument already set - return false; + return nullptr; } // Add the argument to |argv| and perform type checks. auto result = CheckArgument(call, arglist[argidx], param, &ps, argpos); if (!result) - return false; + return nullptr; ps.argv[argpos] = result; nargs++; @@ -2012,7 +1765,7 @@ bool Semantics::CheckCallExpr(CallExpr* call) { if (!sc_->func()) { report(call, 10); - return false; + return nullptr; } // Check for missing or invalid extra arguments, and fill in default @@ -2024,37 +1777,28 @@ bool Semantics::CheckCallExpr(CallExpr* call) { if (argidx >= ps.argv.size() || !ps.argv[argidx]) { auto result = CheckArgument(call, arg, nullptr, &ps, argidx); if (!result) - return false; + return nullptr; ps.argv[argidx] = result; } +#if 0 Expr* expr = ps.argv[argidx]; if (expr->as() && !IsReferenceType(iVARIABLE, arg->type())) { - UserOperation userop; - if (find_userop(*sc_, 0, arg->default_value()->type, arg->type(), 2, nullptr, - &userop)) - { - ps.argv[argidx] = new CallUserOpExpr(userop, expr); - } + assert(false); } +#endif } - // Copy newly deduced argument information. - if (call->args().size() == ps.argv.size()) { - for (size_t i = 0; i < ps.argv.size(); i++) - call->args()[i] = ps.argv[i]; - } else { - new (&call->args()) PoolArray(ps.argv); - } - return true; + return new ir::CallOp(call, QualType(decl->return_type()), fun, ps.argv); } -Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, - ParamState* ps, unsigned int pos) +ir::Value* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, + ParamState* ps, unsigned int pos) { while (pos >= ps->argv.size()) ps->argv.push_back(nullptr); +#if 0 unsigned int visual_pos = call->implicit_this() ? pos : pos + 1; if (!param || param->as()) { @@ -2082,9 +1826,11 @@ Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, // The rest of the code to handle default values is in DoEmit. return param; } +#endif + ir::Value* val = nullptr; if (param != call->implicit_this()) { - if (!CheckRvalue(param)) + if (val = CheckExpr(param); !val) return nullptr; } @@ -2093,37 +1839,29 @@ Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, bool handling_this = call->implicit_this() && (pos == 0); if (param->val().ident == iACCESSOR) { - if (!CheckRvalue(param->pos(), param->val())) +#if 0 + if (val = CheckRvalue(val); !val) return nullptr; - param = new RvalueExpr(param); +#endif + assert(false); } - const auto* val = ¶m->val(); - bool lvalue = param->lvalue(); if (arg->type_info().is_varargs) { assert(!handling_this); // Always pass by reference. - if (val->ident == iVARIABLE) { - if (val->sym->is_const() && !arg->type_info().is_const) { - // Treat a "const" variable passed to a function with a - // non-const "variable argument list" as a constant here. - if (!lvalue) { - report(param, 22); // need lvalue - return nullptr; - } - NeedsHeapAlloc(param); - } else if (!lvalue) { - NeedsHeapAlloc(param); - } - } else if (val->ident == iCONSTEXPR || val->ident == iEXPRESSION) { + if (!val->type()->isReferenceType() && !val->as()) { NeedsHeapAlloc(param); + val = new ir::TempRef(param, val); } - if (!checktag_string(arg->type(), val) && !checktag(arg->type(), val->type())) +#if 0 + if (!checktag_string(arg->type(), val) && !checktag(arg->type(), val->type().ptr())) report(param, 213) << arg->type() << val->type(); +#endif } else if (arg->type()->isReference()) { assert(!handling_this); +#if 0 if (!lvalue || val->ident == iARRAYCHAR) { report(param, 35) << visual_pos; // argument type mismatch return nullptr; @@ -2133,7 +1871,9 @@ Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, return nullptr; } checktag(arg->type()->inner(), val->type()); +#endif } else if (arg->type()->isArray()) { +#if 0 // If the input type is an index into an array, create an implicit // array type to represent the slice. Type* type = val->type(); @@ -2148,12 +1888,12 @@ Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, report(param, 35) << visual_pos; // argument type mismatch return nullptr; } +#endif } else { - if (lvalue) { - param = new RvalueExpr(param); - val = ¶m->val(); - } + if (auto lval = val->as(); lval) + val = new ir::Load(param, val->type(), lval); +#if 0 // Do not allow user operators to transform |this|. UserOperation userop; if (!handling_this && @@ -2166,8 +1906,9 @@ Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, TypeChecker tc(param, arg->type(), val->type(), TypeChecker::Argument); if (!tc.Coerce()) return nullptr; +#endif } - return param; + return val; } void @@ -2185,50 +1926,46 @@ CallExpr::MarkUsed(SemaContext& sc) } bool Semantics::CheckStaticAssertStmt(StaticAssertStmt* stmt) { - auto expr = stmt->expr(); - if (!CheckExpr(expr)) + auto val = CheckRvalue(stmt->expr()); + if (!val) return false; // :TODO: insert coercion to bool. - cell value; - Type* type; - if (!expr->EvalConst(&value, &type)) { - report(expr, 8); + auto cv = val->as(); + if (!cv) { + report(stmt->expr(), 8); return false; } - if (value) + if (cv->value()) return true; std::string message; if (stmt->text()) message += ": " + std::string(stmt->text()->chars(), stmt->text()->length()); - report(expr, 70) << message; + report(stmt, 70) << message; return false; } -bool Semantics::CheckNewArrayExpr(NewArrayExpr* expr) { +ir::Value* Semantics::CheckNewArrayExpr(NewArrayExpr* expr) { // We can't handle random refarrays floating around yet, so forbid this. report(expr, 142); - return false; + return nullptr; } bool Semantics::CheckNewArrayExprForArrayInitializer(NewArrayExpr* na) { +#if 0 if (na->analyzed()) return na->analysis_result(); na->set_analysis_result(false); - auto& val = na->val(); - val.ident = iEXPRESSION; - PoolList dims; for (auto& expr : na->exprs()) { - if (!CheckRvalue(expr)) + auto val = CheckExpr(expr); + if (!val) return false; - if (expr->lvalue()) - expr = new RvalueExpr(expr); const auto& v = expr->val(); if (IsLegacyEnumType(sc_->scope(), v.type())) { @@ -2239,7 +1976,9 @@ bool Semantics::CheckNewArrayExprForArrayInitializer(NewArrayExpr* na) { report(expr, 77) << v.type(); return false; } - if (v.ident == iCONSTEXPR && v.constval() <= 0) { + + auto cv = val->as(); + if (cv && cv->value() <= 0) { report(expr, 9); return false; } @@ -2248,6 +1987,8 @@ bool Semantics::CheckNewArrayExprForArrayInitializer(NewArrayExpr* na) { assert(na->type()->isArray()); na->set_analysis_result(true); +#endif + assert(false); return true; } @@ -2259,22 +2000,32 @@ NewArrayExpr::ProcessUses(SemaContext& sc) } bool Semantics::CheckIfStmt(IfStmt* stmt) { - if (Expr* expr = AnalyzeForTest(stmt->cond())) - stmt->set_cond(expr); + ir::Value* cond = AnalyzeForTest(stmt->cond()); + if (!cond) + return false; // Note: unlike loop conditions, we don't factor in constexprs here, it's // too much work and way less common than constant loop conditions. + ir::StreamNode* on_true = nullptr; + ir::StreamNode* on_false = nullptr; ke::Maybe always_returns; { AutoCollectSemaFlow flow(*sc_, &always_returns); + + ir::NodeListBuilder builder(&ir_); if (!CheckStmt(stmt->on_true(), STMT_OWNS_HEAP)) return false; + on_true = builder.Finish(); } { AutoCollectSemaFlow flow(*sc_, &always_returns); - if (stmt->on_false() && !CheckStmt(stmt->on_false(), STMT_OWNS_HEAP)) - return false; + if (stmt->on_false()) { + ir::NodeListBuilder builder(&ir_); + if (!CheckStmt(stmt->on_false(), STMT_OWNS_HEAP)) + return false; + on_false = builder.Finish(); + } } if (stmt->on_false()) { @@ -2294,15 +2045,20 @@ bool Semantics::CheckIfStmt(IfStmt* stmt) { if (*always_returns) sc_->set_always_returns(true); + + ir_->emplace(stmt, cond, on_true, on_false); return true; } bool Semantics::CheckExprStmt(ExprStmt* stmt) { - auto expr = stmt->expr(); - if (!CheckExpr(expr)) + auto val = CheckRvalue(stmt->expr()); + if (!val) return false; - if (!expr->HasSideEffects()) - report(expr, 215); + + if (!val->HasSideEffects()) + report(stmt, 215); + + ir_->emplace(stmt, val); return true; } @@ -2419,11 +2175,13 @@ AutoCollectSemaFlow::~AutoCollectSemaFlow() bool Semantics::CheckBreakStmt(BreakStmt* stmt) { sc_->loop_has_break() = true; + ir_->emplace(stmt); return true; } bool Semantics::CheckContinueStmt(ContinueStmt* stmt) { sc_->loop_has_continue() = true; + ir_->emplace(stmt); return true; } @@ -2437,6 +2195,9 @@ bool Semantics::CheckReturnStmt(ReturnStmt* stmt) { if (!expr) { if (fun->MustReturnValue()) ReportFunctionReturnError(fun); + + ir_->emplace(stmt, nullptr); + if (sc_->void_return()) return true; sc_->set_void_return(stmt); @@ -2451,12 +2212,10 @@ bool Semantics::CheckReturnStmt(ReturnStmt* stmt) { } } - if (!CheckRvalue(expr)) + auto val = CheckRvalue(expr); + if (!val) return false; - if (expr->lvalue()) - expr = stmt->set_expr(new RvalueExpr(expr)); - AutoErrorPos aep(expr->pos()); if (fun->return_type()->isVoid()) { @@ -2466,17 +2225,17 @@ bool Semantics::CheckReturnStmt(ReturnStmt* stmt) { sc_->set_returns_value(); - const auto& v = expr->val(); - // Check that the return statement matches the declared return type. - TypeChecker tc(stmt, fun->return_type(), v.type(), TypeChecker::Return); + TypeChecker tc(stmt, QualType(fun->return_type()), val->type(), TypeChecker::Return); if (!tc.Coerce()) return false; - if (v.type()->isArray() || v.type()->isEnumStruct()) { + if (val->type()->isArray() || val->type()->isEnumStruct()) { if (!CheckCompoundReturnStmt(stmt)) return false; } + + ir_->emplace(stmt, val); return true; } @@ -2522,51 +2281,25 @@ bool Semantics::CheckCompoundReturnStmt(ReturnStmt* stmt) { } bool Semantics::CheckAssertStmt(AssertStmt* stmt) { - if (Expr* expr = AnalyzeForTest(stmt->expr())) { - stmt->set_expr(expr); - return true; - } - return false; + auto val = AnalyzeForTest(stmt->expr()); + if (!val) + return false; + + ir_->emplace(stmt, val); + return true; } bool Semantics::CheckDeleteStmt(DeleteStmt* stmt) { - auto expr = stmt->expr(); - if (!CheckRvalue(expr)) + auto val = CheckExpr(stmt->expr()); + if (!val) return false; - const auto& v = expr->val(); - switch (v.ident) { - case iFUNCTN: - report(expr, 167) << "function"; - return false; - - case iVARIABLE: - if (v.type()->isArray() || v.type()->isEnumStruct()) { - report(expr, 167) << v.type(); - return false; - } - break; - - case iACCESSOR: - if (v.accessor()->getter()) - markusage(v.accessor()->getter(), uREAD); - if (v.accessor()->setter()) - markusage(v.accessor()->setter(), uREAD); - break; - } - - Type* type = v.type(); - if (type->isReference()) - type = type->inner(); - - if (type->isInt()) { - report(expr, 167) << "integers"; - return false; - } + MarkUsage(val, uREAD | uWRITTEN); + auto type = BuildRvalueType(val->type()); auto map = type->asMethodmap(); if (!map) { - report(expr, 115) << "type" << v.type(); + report(stmt, 115) << "type" << type; return false; } @@ -2578,51 +2311,46 @@ bool Semantics::CheckDeleteStmt(DeleteStmt* stmt) { } if (!map || !map->dtor()) { - report(expr, 115) << "methodmap" << map->name(); + report(stmt, 115) << "methodmap" << map->name(); return false; } markusage(map->dtor(), uREAD); - stmt->set_map(map); + ir_->emplace(stmt, val, map->dtor()); return true; } bool Semantics::CheckExitStmt(ExitStmt* stmt) { - auto expr = stmt->expr(); - if (!CheckRvalue(expr)) + auto val = CheckRvalue(stmt->expr()); + if (!val) return false; - if (expr->lvalue()) - expr = stmt->set_expr(new RvalueExpr(expr)); - if (!IsValueKind(expr->val().ident)) { - report(expr, 106); + AutoErrorPos aep(stmt->pos()); + if (!TypeChecker::DoCoerce(stmt->expr()->pos(), types_->get_int(), val->type())) return false; - } - - AutoErrorPos aep(expr->pos()); - if (!TypeChecker::DoCoerce(types_->type_int(), expr)) - return false; + ir_->emplace(stmt, val); return true; } bool Semantics::CheckDoWhileStmt(DoWhileStmt* stmt) { + ir::Value* cond; { ke::SaveAndSet restore_heap(&pending_heap_allocation_, false); - if (Expr* expr = AnalyzeForTest(stmt->cond())) { - stmt->set_cond(expr); - AssignHeapOwnership(expr); - } + if ((cond = AnalyzeForTest(stmt->cond())) == nullptr) + return false; +#if 0 + AssignHeapOwnership(stmt->cond()); +#endif } - auto cond = stmt->cond(); - ke::Maybe constval; - if (cond->val().ident == iCONSTEXPR) - constval.init(cond->val().constval()); + if (auto cv = cond->as()) + constval.init(cv->value()); + ir::StreamNode* body; bool has_break = false; bool has_return = false; ke::Maybe always_returns; @@ -2631,8 +2359,10 @@ bool Semantics::CheckDoWhileStmt(DoWhileStmt* stmt) { ke::SaveAndSet auto_break(&sc_->loop_has_break(), false); ke::SaveAndSet auto_return(&sc_->loop_has_return(), false); + ir::NodeListBuilder builder(&ir_); if (!CheckStmt(stmt->body(), STMT_OWNS_HEAP)) return false; + body = builder.Finish(); has_break = sc_->loop_has_break(); has_return = sc_->loop_has_return(); @@ -2654,33 +2384,46 @@ bool Semantics::CheckDoWhileStmt(DoWhileStmt* stmt) { } // :TODO: endless loop warning? + ir_->emplace(stmt, cond, body); return true; } bool Semantics::CheckForStmt(ForStmt* stmt) { bool ok = true; - if (stmt->init() && !CheckStmt(stmt->init())) - ok = false; - auto cond = stmt->cond(); - if (cond) { - if (Expr* expr = AnalyzeForTest(cond)) - cond = stmt->set_cond(expr); + ir::StreamNode* init = nullptr; + if (stmt->init()) { + ir::NodeListBuilder builder(&ir_); + if (CheckStmt(stmt->init())) + init = builder.Finish(); else ok = false; } + + ir::Value* cond = nullptr; + if (stmt->cond()) { + if ((cond = AnalyzeForTest(stmt->cond())) == nullptr) + ok = false; + } + + ir::Value* advance = nullptr; if (stmt->advance()) { ke::SaveAndSet restore(&pending_heap_allocation_, false); - if (CheckRvalue(stmt->advance())) + if ((advance = CheckExpr(stmt->advance())) != nullptr) { + // :TODO: check if has effect +#if 0 AssignHeapOwnership(stmt->advance()); - else +#endif + } else { ok = false; + } } ke::Maybe constval; - if (cond && cond->val().ident == iCONSTEXPR) - constval.init(cond->val().constval()); + if (auto cv = cond->as()) + constval.init(cv->value()); + ir::StreamNode* body_ir; bool has_break = false; bool has_return = false; ke::Maybe always_returns; @@ -2690,7 +2433,11 @@ bool Semantics::CheckForStmt(ForStmt* stmt) { ke::SaveAndSet auto_continue(&sc_->loop_has_continue(), false); ke::SaveAndSet auto_return(&sc_->loop_has_return(), false); - ok &= CheckStmt(stmt->body(), STMT_OWNS_HEAP); + ir::NodeListBuilder builder(&ir_); + if (CheckStmt(stmt->body(), STMT_OWNS_HEAP)) + body_ir = builder.Finish(); + else + ok = false; has_break = sc_->loop_has_break(); has_return = sc_->loop_has_return(); @@ -2721,18 +2468,21 @@ bool Semantics::CheckForStmt(ForStmt* stmt) { if (stmt->scope()) TestSymbols(stmt->scope(), true); + + ir_->emplace(stmt, init, cond, advance, body_ir); return ok; } bool Semantics::CheckSwitchStmt(SwitchStmt* stmt) { - auto expr = stmt->expr(); - bool tag_ok = CheckRvalue(expr); + auto cond = CheckRvalue(stmt->expr()); + if (!cond) + return false; + +#if 0 const auto& v = expr->val(); if (tag_ok && v.type()->isArray()) report(expr, 33) << "-unknown-"; - - if (expr->lvalue()) - expr = stmt->set_expr(new RvalueExpr(expr)); +#endif ke::Maybe always_returns; ke::Maybe flow; @@ -2748,38 +2498,53 @@ bool Semantics::CheckSwitchStmt(SwitchStmt* stmt) { } }; - std::unordered_set case_values; + std::unordered_set case_values; + std::vector out_cases;; for (const auto& case_entry : stmt->cases()) { - for (Expr* expr : case_entry.first) { - if (!CheckRvalue(expr)) - continue; + PoolArray entry_values(case_entry.first.size()); + for (size_t i = 0; i < case_entry.first.size(); i++) { + Expr* expr = case_entry.first[i]; + auto val = CheckRvalue(expr); + if (!val) + return false; - cell value; - Type* type; - if (!expr->EvalConst(&value, &type)) { + auto cv = val->as(); + if (!cv) { report(expr, 8); - continue; - } - if (tag_ok) { - AutoErrorPos aep(expr->pos()); - TypeChecker::DoCoerce(expr->pos(), v.type(), type); + return false; } - if (!case_values.count(value)) - case_values.emplace(value); + AutoErrorPos aep(expr->pos()); + TypeChecker::DoCoerce(expr->pos(), cond->type(), cv->type()); + + if (!case_values.count(cv->value())) + case_values.emplace(cv->value()); else - report(expr, 40) << value; + report(expr, 40) << cv->value(); + + entry_values[i] = cv->value(); } AutoCollectSemaFlow flow(*sc_, &always_returns); - if (CheckStmt(case_entry.second)) - update_flow(case_entry.second->flow_type()); + + ir::NodeListBuilder builder(&ir_); + if (!CheckStmt(case_entry.second)) + return false; + out_cases.emplace_back(std::move(entry_values), builder.Finish()); + + update_flow(case_entry.second->flow_type()); } + ir::StreamNode* default_case = nullptr; if (stmt->default_case()) { AutoCollectSemaFlow flow(*sc_, &always_returns); - if (CheckStmt(stmt->default_case())) - update_flow(stmt->default_case()->flow_type()); + + ir::NodeListBuilder builder(&ir_); + if (!CheckStmt(stmt->default_case())) + return false; + default_case = builder.Finish(); + + update_flow(stmt->default_case()->flow_type()); } else { always_returns.init(false); update_flow(Flow_None); @@ -2790,7 +2555,7 @@ bool Semantics::CheckSwitchStmt(SwitchStmt* stmt) { stmt->set_flow_type(*flow); - // Return value doesn't really matter for statements. + ir_->emplace(stmt, cond, std::move(out_cases), default_case); return true; } @@ -2855,6 +2620,17 @@ bool Semantics::CheckFunctionDeclImpl(FunctionDecl* info) { report(info->pos(), 141); } + auto fun = BuildFunction(info); + ke::SaveAndSet push_fun(&fun_, fun); + + bool ok = true; + if (info->body()) { + ir::NodeListBuilder builder(&ir_); + ok = CheckStmt(info->body(), STMT_OWNS_HEAP); + fun->set_body(builder.Finish()); + } + +#if 0 if (info->is_native()) { if (decl.type.dim_exprs.size() > 0) { report(info->pos(), 83); @@ -2916,6 +2692,33 @@ bool Semantics::CheckFunctionDeclImpl(FunctionDecl* info) { if (info->is_public()) cc_.publics().emplace(info->canonical()); return ok; +#endif + return ok; +} + +ir::Function* Semantics::BuildFunction(FunctionDecl* decl) { + auto canonical = decl->canonical(); + + if (auto iter = functions_.find(canonical); iter != functions_.end()) + return iter->second; + + auto fun = new ir::Function(canonical); + functions_.emplace(canonical, fun); + mod_->functions().emplace_back(fun); + return fun; +} + +ir::FunctionRef* Semantics::BuildFunctionRef(Expr* expr, ir::Function* fun) { + fun_->AddReferenceTo(fun); +#if 0 + // :TODO: proper type +#endif + return new ir::FunctionRef(expr, QualType(fun->decl()->return_type()), fun); +} + +ir::FunctionRef* Semantics::BuildFunctionRef(Expr* expr, FunctionDecl* decl) { + auto fun = BuildFunction(decl); + return BuildFunctionRef(expr, fun); } void Semantics::CheckFunctionReturnUsage(FunctionDecl* info) { @@ -3153,12 +2956,15 @@ void fill_arg_defvalue(CompileContext& cc, ArgDecl* decl) { def->sym = sym->as(); } else { + assert(false); +#if 0 auto array = cc.NewDefaultArrayData(); BuildCompoundInitializer(decl, array, 0); def->array = array; def->array->iv_size = (cell_t)array->iv.size(); def->array->data_size = (cell_t)array->data.size(); +#endif } decl->set_default_value(def); } @@ -3178,23 +2984,21 @@ SymbolScope* Semantics::current_scope() const { // Determine the set of live functions. void Semantics::DeduceLiveness() { - std::vector work; - std::unordered_set seen; + std::vector work; + std::unordered_set seen; // The root set is all public functions. - for (const auto& decl : cc_.publics()) { - assert(!decl->is_native()); - assert(decl->is_public()); + for (const auto& fun : mod_->functions()) { + if (fun->decl()->is_public()) + fun->set_is_live(); - decl->set_is_live(); - - seen.emplace(decl); - work.emplace_back(decl); + seen.emplace(fun); + work.emplace_back(fun); } // Traverse referrers to find the transitive set of live functions. while (!work.empty()) { - FunctionDecl* live = ke::PopBack(&work); + auto live = ke::PopBack(&work); if (!live->refers_to()) continue; @@ -3234,26 +3038,180 @@ void Semantics::DeduceMaybeUsed() { } } -bool Semantics::CheckRvalue(Expr* expr) { - if (!CheckExpr(expr)) - return false; - return CheckRvalue(expr->pos(), expr->val()); +QualType Semantics::BuildRvalueType(QualType type) { + if (type->isReference()) + return QualType(type->inner()); + return type; +} + +ir::Value* Semantics::BuildRvalue(Expr* expr, ir::Lvalue* lval) { + QualType type = lval->type(); + if (lval->as()) + type = BuildRvalueType(type); + + assert(!type->isReference()); + return new ir::Load(expr, type, lval); +} + +ir::Value* Semantics::CheckRvalue(Expr* expr) { + auto val = CheckExpr(expr); + if (!val) + return nullptr; + + if (auto lval = val->as()) + return BuildRvalue(expr, lval); + + return val; } -bool Semantics::CheckRvalue(const token_pos_t& pos, const value& val) { - if (auto accessor = val.accessor()) { - if (!accessor->getter()) { - report(pos, 149) << accessor->name(); +static inline std::string_view GetOverloadName(int token) { + switch (token) { + case '=': return "="; + case '*': return "*"; + case taMULT: return "*"; + case '/': return "/"; + case taDIV: return "/"; + case '%': return "%"; + case taMOD: return "%"; + case '+': return "+"; + case taADD: return "+"; + case '-': return "-"; + case taSUB: return "-"; + case '<': return "<"; + case '>': return ">"; + case tlLE: return "<="; + case tlGE: return ">="; + case tlEQ: return "=="; + case tlNE: return "!="; + case tINC: return "++"; + case tDEC: return "--"; + case '!': return "!"; + default: return {}; + } +} + + +static inline bool MatchOperator(int token, FunctionDecl* fun, QualType type1, QualType type2) { + // If token is '=', type2 is the return type. + size_t numparam = (type2 && token != '=') ? 2 : 1; + + const auto& args = fun->args(); + if (args.size() != numparam) + return false; + + assert(numparam == 1 || numparam == 2); + QualType types[2] = { type1, type2 }; + + for (size_t i = 0; i < numparam; i++) { + if (args[i]->type_info().is_varargs) + return false; + if (QualType(args[i]->type_info().type) != types[i]) return false; - } } + + if (token == '=' && QualType(fun->return_type()) != type2) + return false; return true; } +ir::Value* Semantics::MaybeCallUserOp(Expr* expr, int token, ir::Value* first, + ir::Value* second) +{ + // No operator overloading allowed in the preprocessor. + if (cc_.in_preprocessor()) + return nullptr; + + // No overloads for int,int. + QualType type1 = first->type(); + QualType type2; + if (second) + type2 = second->type(); + + // :TODO: use TypeChecker to do this instead + if (type1->isReference()) + type1 = QualType(type1->inner()); + if (type2 && type2->isReference()) + type2 = QualType(type2->inner()); + + if (type1->isInt() || (type2 && type2->isInt())) + return nullptr; + + auto opername = GetOverloadName(token); + if (opername.empty()) + return nullptr; + + auto atom = cc_.atom(opername); + Decl* chain = FindSymbol(*sc_, atom); + if (!chain) + return nullptr; + + FunctionDecl* decl = nullptr; + bool swapparams = false; + bool is_commutative = IsOperTokenCommutative(token); + for (auto iter = chain; iter; iter = iter->next) { + auto fun = iter->as(); + if (!fun) + continue; + fun = fun->canonical(); + + bool matched = MatchOperator(token, fun, type1, type2); + bool swapped = false; + if (!matched && is_commutative && type1 != type2 && token) { + matched = MatchOperator(token, fun, type2, type1); + swapped = true; + } + if (matched) { + decl = fun; + swapparams = swapped; + break; + } + } + + if (!decl) + return nullptr; + + // we don't want to use the redefined operator in the function that + // redefines the operator itself, otherwise the snippet below gives + // an unexpected recursion: + // fixed:operator+(fixed:a, fixed:b) + // return a + b + if (decl == sc_->func()) + report(expr, 408); + + markusage(decl, uREAD); + + return new ir::CallUserOp(expr, QualType(decl->return_type()), decl, first, second, + swapparams); +} + void DeleteStmt::ProcessUses(SemaContext& sc) { expr_->MarkAndProcessUses(sc); markusage(map_->dtor(), uREAD); } +void Semantics::MarkUsage(ir::Value* val, uint8_t usage) { + switch (val->kind()) { + case IrKind::PropertyRef: { + auto prop = val->to(); + if (usage & uREAD) + markusage(prop->decl()->getter(), uREAD); + if (usage & uWRITTEN) + markusage(prop->decl()->setter(), uWRITTEN); + break; + } + case IrKind::VariableRef: { + auto ref = val->to(); + markusage(ref->var()->decl(), usage); + break; + } + case IrKind::FunctionRef: { + assert(false); + break; + } + default: + break; + } +} + } // namespace cc } // namespace sp diff --git a/compiler/semantics.h b/compiler/semantics.h index d5518f100..e17a78415 100644 --- a/compiler/semantics.h +++ b/compiler/semantics.h @@ -24,6 +24,7 @@ #include #include "compile-context.h" +#include "ir.h" #include "sc.h" #include "scopes.h" #include "parse-node.h" @@ -97,6 +98,7 @@ class SemaContext bool& loop_has_break() { return loop_has_break_; } bool& loop_has_continue() { return loop_has_continue_; } bool& loop_has_return() { return loop_has_return_; } + FlowType& flow_type() { return flow_type_; } bool warned_unreachable() const { return warned_unreachable_; } void set_warned_unreachable() { warned_unreachable_ = true; } @@ -118,6 +120,7 @@ class SemaContext bool preprocessing() const { return preprocessing_; } std::unordered_set& static_scopes() { return static_scopes_; } + std::unordered_map& local_vars() { return local_vars_; } private: CompileContext& cc_; @@ -135,7 +138,9 @@ class SemaContext bool warned_unreachable_ = false; bool preprocessing_ = false; SemaContext* cc_prev_sc_ = nullptr; + FlowType flow_type_ = Flow_None; std::unordered_set static_scopes_; + std::unordered_map local_vars_; }; class Semantics final @@ -148,7 +153,7 @@ class Semantics final friend class Parser; public: - explicit Semantics(CompileContext& cc); + Semantics(CompileContext& cc, std::shared_ptr mod); bool Analyze(ParseTree* tree); @@ -172,74 +177,79 @@ class Semantics final bool CheckFunctionDecl(FunctionDecl* info); bool CheckFunctionDeclImpl(FunctionDecl* info); void CheckFunctionReturnUsage(FunctionDecl* info); - bool CheckPragmaUnusedStmt(PragmaUnusedStmt* stmt); - bool CheckSwitchStmt(SwitchStmt* stmt); - bool CheckForStmt(ForStmt* stmt); - bool CheckDoWhileStmt(DoWhileStmt* stmt); + bool CheckVarDecl(VarDeclBase* decl); + bool CheckPstructDecl(VarDeclBase* decl); + bool CheckPstructArg(VarDeclBase* decl, PstructDecl* ps, StructInitFieldExpr* field, + std::vector* visited); + + // Ported to IR. + bool CheckAssertStmt(AssertStmt* stmt); bool CheckBreakStmt(BreakStmt* stmt); + bool CheckCompoundReturnStmt(ReturnStmt* stmt); bool CheckContinueStmt(ContinueStmt* stmt); - bool CheckExitStmt(ExitStmt* stmt); bool CheckDeleteStmt(DeleteStmt* stmt); - bool CheckAssertStmt(AssertStmt* stmt); - bool CheckStaticAssertStmt(StaticAssertStmt* stmt); - bool CheckReturnStmt(ReturnStmt* stmt); - bool CheckCompoundReturnStmt(ReturnStmt* stmt); + bool CheckDoWhileStmt(DoWhileStmt* stmt); + bool CheckExitStmt(ExitStmt* stmt); bool CheckExprStmt(ExprStmt* stmt); + bool CheckForStmt(ForStmt* stmt); bool CheckIfStmt(IfStmt* stmt); - bool CheckConstDecl(ConstDecl* decl); - bool CheckVarDecl(VarDeclBase* decl); - bool CheckConstDecl(VarDecl* decl); - bool CheckPstructDecl(VarDeclBase* decl); - bool CheckPstructArg(VarDeclBase* decl, PstructDecl* ps, StructInitFieldExpr* field, - std::vector* visited); + bool CheckPragmaUnusedStmt(PragmaUnusedStmt* stmt); + bool CheckReturnStmt(ReturnStmt* stmt); + bool CheckSwitchStmt(SwitchStmt* stmt); + bool CheckStaticAssertStmt(StaticAssertStmt* stmt); // Expressions. - bool CheckExpr(Expr* expr); - bool CheckNewArrayExpr(NewArrayExpr* expr); - bool CheckArrayExpr(ArrayExpr* expr); - bool CheckStringExpr(StringExpr* expr); - bool CheckTaggedValueExpr(TaggedValueExpr* expr); - bool CheckNullExpr(NullExpr* expr); - bool CheckThisExpr(ThisExpr* expr); - bool CheckCommaExpr(CommaExpr* expr); - bool CheckIndexExpr(IndexExpr* expr); - bool CheckCallExpr(CallExpr* expr); - bool CheckSymbolExpr(SymbolExpr* expr, bool allow_types); - bool CheckSizeofExpr(SizeofExpr* expr); - bool CheckCastExpr(CastExpr* expr); - bool CheckIncDecExpr(IncDecExpr* expr); - bool CheckTernaryExpr(TernaryExpr* expr); - bool CheckChainedCompareExpr(ChainedCompareExpr* expr); - bool CheckLogicalExpr(LogicalExpr* expr); - bool CheckBinaryExpr(BinaryExpr* expr); - bool CheckUnaryExpr(UnaryExpr* expr); - bool CheckFieldAccessExpr(FieldAccessExpr* expr, bool from_call); - bool CheckStaticFieldAccessExpr(FieldAccessExpr* expr); - bool CheckEnumStructFieldAccessExpr(FieldAccessExpr* expr, Type* type, EnumStructDecl* root, - bool from_call); - bool CheckRvalue(Expr* expr); - bool CheckRvalue(const token_pos_t& pos, const value& val); - - bool CheckAssignmentLHS(BinaryExpr* expr); - bool CheckAssignmentRHS(BinaryExpr* expr); + ir::Value* CheckExpr(Expr* expr); + ir::Value* CheckNewArrayExpr(NewArrayExpr* expr); + ir::Value* CheckArrayExpr(ArrayExpr* expr); + ir::Value* CheckStringExpr(StringExpr* expr); + ir::Value* CheckTaggedValueExpr(TaggedValueExpr* expr); + ir::Value* CheckNullExpr(NullExpr* expr); + ir::Value* CheckThisExpr(ThisExpr* expr); + ir::Value* CheckCommaExpr(CommaExpr* expr); + ir::Value* CheckIndexExpr(IndexExpr* expr); + ir::Value* CheckCallExpr(CallExpr* expr); + ir::Value* CheckSymbolExpr(SymbolExpr* expr, bool allow_types); + ir::Value* CheckSizeofExpr(SizeofExpr* expr); + ir::Value* CheckCastExpr(CastExpr* expr); + ir::Value* CheckIncDecExpr(IncDecExpr* expr); + ir::Value* CheckTernaryExpr(TernaryExpr* expr); + ir::Value* CheckChainedCompareExpr(ChainedCompareExpr* expr); + ir::Value* CheckLogicalExpr(LogicalExpr* expr); + ir::Value* CheckBinaryExpr(BinaryExpr* expr); + ir::Value* CheckUnaryExpr(UnaryExpr* expr); + ir::Value* CheckFieldAccessExpr(FieldAccessExpr* expr, bool from_call); + ir::Value* CheckStaticFieldAccessExpr(FieldAccessExpr* expr); + ir::Value* CheckEnumStructFieldAccessExpr(FieldAccessExpr* expr, ir::Value* base, + EnumStructDecl* root, bool from_call); + ir::Value* CheckRvalue(Expr* expr); + ir::Value* BuildRvalue(Expr* expr, ir::Lvalue* val); + QualType BuildRvalueType(QualType type); + ir::FunctionRef* BuildFunctionRef(Expr* expr, ir::Function* fun); + ir::FunctionRef* BuildFunctionRef(Expr* expr, FunctionDecl* decl); + void MarkUsage(ir::Value* val, uint8_t usage); + + bool CheckAssignmentLHS(BinaryExpr* expr, ir::Lvalue* lval); + bool CheckAssignmentRHS(BinaryExpr* expr, ir::Lvalue* lval, ir::Value* rval); bool AddImplicitDynamicInitializer(VarDeclBase* decl); struct ParamState { - std::vector argv; + std::vector argv; }; bool CheckArrayDeclaration(VarDeclBase* decl); bool CheckNewArrayExprForArrayInitializer(NewArrayExpr* expr); - Expr* CheckArgument(CallExpr* call, ArgDecl* arg, Expr* expr, - ParamState* ps, unsigned int argpos); - bool CheckWrappedExpr(Expr* outer, Expr* inner); - FunctionDecl* BindNewTarget(Expr* target); - FunctionDecl* BindCallTarget(CallExpr* call, Expr* target); + ir::Value* CheckArgument(CallExpr* call, ArgDecl* arg, Expr* expr, ParamState* ps, + unsigned int argpos); + ir::Function* BindNewTarget(Expr* target); + ir::Function* BindCallTarget(CallExpr* call, Expr* target); void NeedsHeapAlloc(Expr* expr); void AssignHeapOwnership(ParseNode* node); - Expr* AnalyzeForTest(Expr* expr); + ir::Value* AnalyzeForTest(Expr* expr); + ir::Value* MaybeCallUserOp(Expr* expr, int token, ir::Value* first, ir::Value* second); + ir::Function* BuildFunction(FunctionDecl* decl); void DeduceLiveness(); void DeduceMaybeUsed(); @@ -250,6 +260,7 @@ class Semantics final void CheckVoidDecl(const declinfo_t* decl, int variable); bool CheckScalarType(Expr* expr); + bool CheckScalarType(Expr* expr, QualType type); private: CompileContext& cc_; @@ -257,7 +268,12 @@ class Semantics final tr::unordered_set static_scopes_; tr::vector maybe_used_; SemaContext* sc_ = nullptr; + ir::Function* fun_ = nullptr; + std::shared_ptr mod_; + std::unordered_map functions_; + std::unordered_map global_vars_; bool pending_heap_allocation_ = false; + ir::NodeListBuilder* ir_ = nullptr; }; class AutoEnterScope final diff --git a/compiler/smx-assembly-buffer.h b/compiler/smx-assembly-buffer.h index 82762a2f6..0e3005858 100644 --- a/compiler/smx-assembly-buffer.h +++ b/compiler/smx-assembly-buffer.h @@ -17,12 +17,14 @@ // SourcePawn. If not, see http://www.gnu.org/licenses/. #pragma once -#include "shared/byte-buffer.h" #include #include #include "label.h" +#include "ir.h" +#include "parse-node.h" #include "sctracker.h" +#include "shared/byte-buffer.h" #include "symbols.h" namespace sp { @@ -171,14 +173,15 @@ class SmxAssemblyBuffer : public ByteBuffer } } - void copyarray(VarDeclBase* sym, cell size) { - if (sym->type()->isArray()) { - assert(sym->vclass() == sLOCAL || sym->vclass() == sARGUMENT); // symbol must be stack relative - emit(OP_LOAD_S_ALT, sym->addr()); - } else if (sym->vclass() == sLOCAL || sym->vclass() == sARGUMENT) { - emit(OP_ADDR_ALT, sym->addr()); + void copyarray(ir::Variable* var, cell size) { + auto decl = var->decl(); + if (decl->type()->isArray()) { + assert(decl->vclass() == sLOCAL || decl->vclass() == sARGUMENT); // symbol must be stack relative + emit(OP_LOAD_S_ALT, var->addr()); + } else if (decl->vclass() == sLOCAL || decl->vclass() == sARGUMENT) { + emit(OP_ADDR_ALT, var->addr()); } else { - emit(OP_CONST_ALT, sym->addr()); + emit(OP_CONST_ALT, var->addr()); } emit(OP_MOVS, size); } diff --git a/compiler/type-checker.cpp b/compiler/type-checker.cpp index 888da959b..368137ff4 100644 --- a/compiler/type-checker.cpp +++ b/compiler/type-checker.cpp @@ -309,7 +309,11 @@ bool TypeChecker::DoCoerce(Type* formal, Expr* actual) { } bool TypeChecker::DoCoerce(const token_pos_t& pos, Type* formal, Type* actual, Flags flags) { - TypeChecker tc(pos, QualType(formal), QualType(actual), Generic, flags); + return DoCoerce(pos, QualType(formal), QualType(actual), flags); +} + +bool TypeChecker::DoCoerce(const token_pos_t& pos, QualType formal, QualType actual, Flags flags) { + TypeChecker tc(pos, formal, actual, Generic, flags); return tc.Coerce(); } diff --git a/compiler/type-checker.h b/compiler/type-checker.h index a990b927f..fb657d9db 100644 --- a/compiler/type-checker.h +++ b/compiler/type-checker.h @@ -64,6 +64,7 @@ class TypeChecker { static bool DoCoerce(Type* formal, Expr* actual); static bool DoCoerce(const token_pos_t& pos, Type* formal, Type* actual, Flags flags = None); + static bool DoCoerce(const token_pos_t& pos, QualType formal, QualType actual, Flags flags = None); private: bool CheckImpl(); diff --git a/compiler/types.h b/compiler/types.h index 3aff46afa..4d74fa524 100644 --- a/compiler/types.h +++ b/compiler/types.h @@ -306,6 +306,10 @@ class Type : public PoolObject return pstruct_ptr_; } + bool isReferenceType() const { + return isArray() || isEnumStruct() || isReference(); + } + Type* inner() const { assert(isReference() || isArray()); return inner_type_; @@ -439,6 +443,17 @@ class TypeManager Type* type_char() const { return type_string_; } Type* type_int() const { return type_int_; } + QualType get_object() const { return QualType(type_object_); } + QualType get_null() const { return QualType(type_null_); } + QualType get_function() const { return QualType(type_function_); } + QualType get_any() const { return QualType(type_any_); } + QualType get_void() const { return QualType(type_void_); } + QualType get_float() const { return QualType(type_float_); } + QualType get_bool() const { return QualType(type_bool_); } + QualType get_string() const { return QualType(type_string_); } + QualType get_char() const { return QualType(type_string_); } + QualType get_int() const { return QualType(type_int_); } + private: Type* add(const char* name, TypeKind kind); Type* add(Atom* name, TypeKind kind); diff --git a/shared/string-pool.h b/shared/string-pool.h index 1a953283b..b16bf2384 100644 --- a/shared/string-pool.h +++ b/shared/string-pool.h @@ -19,6 +19,7 @@ #define _include_jitcraft_string_pool_h_ #include +#include #include #include @@ -31,33 +32,6 @@ namespace sp { using namespace ke; -class CharsAndLength -{ - public: - CharsAndLength() - : str_(nullptr), - length_(0) - { - } - - CharsAndLength(const char* str, size_t length) - : str_(str), - length_(length) - { - } - - const char* str() const { - return str_; - } - size_t length() const { - return length_; - } - - private: - const char* str_; - size_t length_; -}; - class StringPool { public: @@ -75,18 +49,18 @@ class StringPool delete* i; } - Atom* add(const std::string& str) { - return add(str.c_str(), str.size()); - } - Atom* add(const char* str, size_t length) { - CharsAndLength chars(str, length); + std::string_view chars(str, length); Table::Insert p = table_.findForAdd(chars); if (!p.found() && !table_.add(p, new Atom(str, length))) return nullptr; return *p; } + Atom* add(std::string_view sv) { + return add(sv.data(), sv.size()); + } + Atom* add(const char* str) { return add(str, strlen(str)); } @@ -99,14 +73,14 @@ class StringPool return HashCharSequence(key, strlen(key)); } - static uint32_t hash(const CharsAndLength& key) { - return HashCharSequence(key.str(), key.length()); + static uint32_t hash(const std::string_view& key) { + return HashCharSequence(key.data(), key.size()); } - static bool matches(const CharsAndLength& key, const Payload& e) { - if (key.length() != e->length()) + static bool matches(const std::string_view& key, const Payload& e) { + if (key.size() != e->length()) return false; - return strncmp(key.str(), e->chars(), key.length()) == 0; + return strncmp(key.data(), e->chars(), key.size()) == 0; } }; diff --git a/third_party/amtl b/third_party/amtl index e38cfe3cb..2d3b1a337 160000 --- a/third_party/amtl +++ b/third_party/amtl @@ -1 +1 @@ -Subproject commit e38cfe3cbf2047916e4a68017840bd325a8f7080 +Subproject commit 2d3b1a3378a3728637f26660c9ffc2df3189cf62 diff --git a/vm/method-verifier.cpp b/vm/method-verifier.cpp index d305a26a6..cb7619ba4 100644 --- a/vm/method-verifier.cpp +++ b/vm/method-verifier.cpp @@ -86,6 +86,7 @@ MethodVerifier::verify() cip_ = reinterpret_cast(block_->start()); while (cip_ < reinterpret_cast(block_->end())) { insn_ = cip_; + //SpewOpcode(stderr, rt_, method_, cip_); OPCODE op = (OPCODE)*cip_++; if (!verifyOp(op)) return nullptr; diff --git a/vm/opcodes.cpp b/vm/opcodes.cpp index b5f00fc60..96ebcb7b9 100644 --- a/vm/opcodes.cpp +++ b/vm/opcodes.cpp @@ -25,8 +25,6 @@ * this exception to all derivative works. AlliedModders LLC defines further * exceptions), found in LICENSE.txt _(as of this writing), version JULY-31-2007)), * or . - * - * Version: $Id$ */ #include "opcodes.h"