From c69d1511ddd38130bfa140379bc0067c66b74bf8 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 29 Jul 2024 21:25:24 -0700 Subject: [PATCH] WIP: Generate an IR. The compiler has come a long way, but we still can't do int64, because it requires a non-cell storage size. There's no (sane) way to express conversions between int32 and int64 within the AST, because we have no uniform way of inserting conversion nodes. This is already a deep problem that has been hacked around for operator overloads and property accessors, and it doesn't scale. The solution is obvious: transform the AST into an IR. That's what we should have done from the beginning but didn't. Unfortunately it requires a *lot* of refactoring and a ton of boilerplate. So far, I have most of the boilerplate done, but the refactoring is only halfway there. CodeGenerator has not been ported to the IR yet. Once this gigantic patch is done, we'll have the following changes: - `struct value` will be eliminated, and good riddance. - The AST will be immutable after parsing. - The semantic analysis phase will output a new IR tree. - CodeGenerator will generate off the IR instead. Since the IR is a transformation of the AST, I'm expecting minimal changes to the end result. - functag_t will be replaced by FunctionType. - Temporary heap allocations will be replaced with pre-allocated stack slots. V2: CG-IR can now assemble trivial programs. V3: CG-IR supports basic calls; 341 test failures. V4: CG-IR supports binary ops; 333 test failures. V5: CG-IR supports do-while and if; 329 test failures. V6: CG-IR supports args, local incdec, switch; 319 test failures. V7: Dropped IncDecOp in favor of Store/BinaryOp primitives. Added global variable support. V8: Added support for heap scopes. 294 test failures. V9: Add support for Load(IndexOp) and arrays as arguments. 290 test failures. V10: Add support for varargs. 289 test failures. V11: Port array helpers to IR. 280 test failures. V12: Add for-loop support. 273 test failures. V13: Get effective address calculation working. 271 test failures. V14: Move array type resolution into the semantic pass, due to not having full semantic powers during name resolution. 250 test failures. V15: Fix argument type deduction. 247 test failures. V16: Fix by-ref arguments. 243 test failures. V17: Add support for "delete". Add support for operator overloads. Introduce the concept of temporary stack slots. 233 test failures. V18: Replace TempRef with StackRef. Implement FieldRef. Implement Paamayim Nekudotayim. 225 test failures. V19: Implement IncDec properly. 222 test failures. V20: Implement PropertyRef. Implement varargs corner cases. 208 test failures. V21: Implement |this|. Implement view_as for some l-values. 204 test failures. V22: Implement bound |this| calls. 188 test failures. V23: Implement trivial view_as. 185 test failures. --- compiler/AMBuilder | 1 + compiler/array-helpers.cpp | 350 ++--- compiler/array-helpers.h | 13 +- compiler/assembler.cpp | 102 +- compiler/assembler.h | 1 + compiler/ast-types.h | 61 + compiler/code-generator.cpp | 1708 ++++++++++++----------- compiler/code-generator.h | 87 +- compiler/compile-context.h | 7 +- compiler/expressions.cpp | 174 +-- compiler/expressions.h | 8 +- compiler/ir.cpp | 139 ++ compiler/ir.h | 990 +++++++++++++ compiler/lexer.cpp | 7 +- compiler/lexer.h | 2 +- compiler/main.cpp | 10 +- compiler/messages.h | 8 +- compiler/name-resolution.cpp | 96 +- compiler/parse-node.cpp | 17 - compiler/parse-node.h | 49 +- compiler/parser.cpp | 25 +- compiler/parser.h | 2 +- compiler/pool-objects.h | 11 +- compiler/qualtype.h | 9 +- compiler/sc.h | 3 +- compiler/semantics.cpp | 2374 +++++++++++++++++--------------- compiler/semantics.h | 176 ++- compiler/smx-assembly-buffer.h | 19 +- compiler/type-checker.cpp | 6 +- compiler/type-checker.h | 1 + compiler/types.h | 16 + shared/string-pool.h | 48 +- vm/method-verifier.cpp | 1 + vm/opcodes.cpp | 2 - 34 files changed, 3953 insertions(+), 2570 deletions(-) create mode 100644 compiler/ir.cpp create mode 100644 compiler/ir.h 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..e498d944c 100644 --- a/compiler/array-helpers.cpp +++ b/compiler/array-helpers.cpp @@ -45,7 +45,7 @@ class ArrayTypeResolver bool ResolveDimExprs(); void ResolveRank(size_t rank, Expr* init); void SetRankSize(Expr* expr, int rank, int size); - bool ResolveDimExpr(Expr* expr, value* v); + ir::Value* ResolveDimExpr(Expr* expr); private: Semantics* sema_; @@ -288,16 +288,17 @@ bool ArrayTypeResolver::ResolveDimExprs() { continue; } - value v; - if (!ResolveDimExpr(expr, &v)) + ir::Value* val = ResolveDimExpr(expr); + if (!val) return false; - if (!IsValidIndexType(v.type())) { - report(expr->pos(), 77) << v.type(); + if (!IsValidIndexType(*val->type())) { + report(expr->pos(), 77) << val->type(); return false; } - if (v.ident != iCONSTEXPR) { + auto cv = val->as(); + if (!cv) { // Non-constant expressions in postdims is illegal for transitional // syntax: // int blah[y]; @@ -316,27 +317,22 @@ bool ArrayTypeResolver::ResolveDimExprs() { // sLOCAL guarantees we have a decl. decl_->set_implicit_dynamic_array(); - } else if (IsLegacyEnumType(sema_->current_scope(), v.type()) && v.sym && - v.sym->as()) - { - report(expr->pos(), 153); - return false; } else { // Constant must be > 0. - if (v.constval() <= 0) { + if (cv->value() <= 0) { report(expr->pos(), 9); return false; } - computed_[i] = v.constval(); + computed_[i] = cv->value(); } } return true; } -bool ArrayTypeResolver::ResolveDimExpr(Expr* expr, value* v) { +ir::Value* ArrayTypeResolver::ResolveDimExpr(Expr* expr) { auto& sc = *sema_->context(); if (!expr->Bind(sc)) - return false; + return nullptr; if (auto sym_expr = expr->as()) { // Special case this: @@ -345,19 +341,11 @@ bool ArrayTypeResolver::ResolveDimExpr(Expr* expr, value* v) { // // For backward compatibility with a huge number of plugins. auto decl = sym_expr->decl(); - if (auto ed = decl->as()) { - *v = {}; - v->set_constval(ed->array_size()); - v->set_type(sc.cc().types()->type_int()); - return true; - } + if (auto ed = decl->as()) + return new ir::Const(expr, sc.cc().types()->get_int(), ed->array_size()); } - if (!sema_->CheckExpr(expr)) - return false; - - *v = expr->val(); - return true; + return sema_->CheckRvalue(expr); } bool ResolveArrayType(Semantics* sema, VarDeclBase* decl) { @@ -394,14 +382,14 @@ class ArrayValidator final es_(nullptr) {} - bool Validate(); + bool Validate(ir::Value** new_init); private: - bool ValidateInitializer(); - bool ValidateRank(ArrayType* rank, Expr* init); - bool ValidateEnumStruct(EnumStructDecl* es, Expr* init); + ir::Value* ValidateInitializer(); + ir::Value* ValidateRank(ArrayType* rank, Expr* init); + ir::Value* ValidateEnumStruct(EnumStructDecl* es, Expr* init); + ir::Value* CheckArgument(SymbolExpr* init); bool AddCells(size_t ncells); - bool CheckArgument(SymbolExpr* init); private: Semantics* sema_; @@ -415,22 +403,36 @@ class ArrayValidator final EnumStructDecl* es_; }; -bool CheckArrayInitialization(Semantics* sema, const typeinfo_t& type, Expr* init) { +bool CheckArrayInitialization(Semantics* sema, const typeinfo_t& type, Expr* init, + ir::Value** new_init) +{ ArrayValidator av(sema, type, init); AutoCountErrors errors; - return av.Validate() && errors.ok(); + + if (!av.Validate(new_init)) { + // Make sure all fail paths return an error. + assert(!errors.ok()); + return false; + } + + // Make sure there were no implicit errors. This should go away when + // matchtag() goes away. + assert(errors.ok()); + return true; } -bool ArrayValidator::Validate() { +bool ArrayValidator::Validate(ir::Value** new_init) { es_ = type_->asEnumStruct(); at_ = type_->as(); if (init_) { - if (!ValidateInitializer()) - return false; - return true; + *new_init = ValidateInitializer(); + return *new_init != nullptr; } + + *new_init = nullptr; + if (!at_) return true; @@ -440,7 +442,7 @@ bool ArrayValidator::Validate() { do { if (!iter->size() && decl_ && decl_->vclass() != sARGUMENT) { report(decl_->pos(), 46) << decl_->name(); - return true; + return false; } iter = iter->inner()->as(); } while (iter); @@ -474,7 +476,7 @@ bool ArrayValidator::Validate() { return true; } -bool ArrayValidator::ValidateInitializer() { +ir::Value* ArrayValidator::ValidateInitializer() { // As a special exception, array arguments can be initialized with a global // reference. if (decl_ && decl_->vclass() == sARGUMENT) { @@ -484,12 +486,11 @@ bool ArrayValidator::ValidateInitializer() { // Handle enum structs here (gross, yes). if (es_) { - if (auto array = init_->as()) { - ValidateEnumStruct(es_, array); - return true; - } + if (auto array = init_->as()) + return ValidateEnumStruct(es_, array); + report(448); - return false; + return nullptr; } // Check for dynamic initializers. @@ -502,11 +503,9 @@ bool ArrayValidator::ValidateInitializer() { TypeChecker tc(ctor, at_, ctor->type(), TypeChecker::Assignment); if (!tc.Check()) - return false; + return nullptr; - if (!sema_->CheckNewArrayExprForArrayInitializer(ctor)) - return false; - return true; + return sema_->CheckNewArrayExprForArrayInitializer(ctor); } // Probably not a dynamic array, check for a fixed initializer. @@ -539,62 +538,68 @@ cell CalcArraySize(Type* type) { return size; } -bool ArrayValidator::CheckArgument(SymbolExpr* expr) { +ir::Value* ArrayValidator::CheckArgument(SymbolExpr* expr) { Decl* decl = expr->decl(); if (!decl) - return false; + return nullptr; VarDecl* var = decl->as(); if (!var) - return false; + return nullptr; assert(var->vclass() == sGLOBAL); TypeChecker tc(expr, type_, var->type(), TypeChecker::Argument); if (!tc.Check()) - return false; + return nullptr; - return true; + // :TODO: make VariableRef + assert(false); + return nullptr; } -bool ArrayValidator::ValidateRank(ArrayType* rank, Expr* init) { +ir::Value* ArrayValidator::ValidateRank(ArrayType* rank, Expr* init) { if (auto next_rank = rank->inner()->as()) { ArrayExpr* array = init->as(); if (!array) { report(init->pos(), 47); - return false; + return nullptr; } if ((cell)array->exprs().size() != rank->size()) { report(init->pos(), 47); - return false; + return nullptr; } if (!AddCells(array->exprs().size())) - return false; + return nullptr; + std::vector values; for (const auto& expr : array->exprs()) { - if (!ValidateRank(next_rank, expr)) - return false; + auto val = ValidateRank(next_rank, expr); + if (!val) + return nullptr; + values.emplace_back(val); } - return true; + return new ir::ArrayInitializer(array, QualType(rank), std::move(values)); } if (StringExpr* str = init->as()) { if (!rank->isCharArray()) { report(init->pos(), 134) << str->val().type() << rank; - return false; + return nullptr; } auto bytes = str->text()->length() + 1; auto cells = char_array_cells(bytes); if (!AddCells(cells)) - return false; + return nullptr; if (rank->size() && bytes > rank->size()) { report(str->pos(), 47); - return false; + return nullptr; } - return true; + + return new ir::CharArrayLiteral(str, QualType(rank)); } cell rank_size = 0; @@ -613,15 +618,15 @@ bool ArrayValidator::ValidateRank(ArrayType* rank, Expr* init) { // int x[10] = 0; if (rank->inner()->isEnumStruct() || !decl_ || at_->inner()->isArray() || !at_->size()) { report(init->pos(), 47); - return false; + return nullptr; } - if (!sema_->CheckExpr(init)) - return false; + if (!sema_->CheckRvalue(init)) + return nullptr; if (init->val().ident != iCONSTEXPR) { report(init->pos(), 47); - return false; + return nullptr; } report(init->pos(), 241); @@ -634,17 +639,20 @@ bool ArrayValidator::ValidateRank(ArrayType* rank, Expr* init) { } if (auto es = rank->inner()->asEnumStruct()) { + std::vector values; for (const auto& expr : array->exprs()) { - if (!ValidateEnumStruct(es, expr)) - return false; + auto val = ValidateEnumStruct(es, expr); + if (!val) + return nullptr; + values.emplace_back(val); } - return true; + return new ir::ArrayInitializer(array, QualType(rank), std::move(values)); } if (rank_size) { if (rank_size < (cell)array->exprs().size()) { report(init->pos(), 47); - return false; + return nullptr; } } else { // There is no actual reason to forbid this, as it works fine in the @@ -652,74 +660,78 @@ bool ArrayValidator::ValidateRank(ArrayType* rank, Expr* init) { // of worms yet. if (decl_ && decl_->vclass() != sARGUMENT && !decl_->type_info().has_postdims) { report(init->pos(), 160); - return false; + return nullptr; } } ke::Maybe prev1, prev2; + std::vector values; for (const auto& expr : array->exprs()) { - if (!sema_->CheckExpr(expr)) - continue; - - AutoErrorPos pos(expr->pos()); - if (expr->as()) { - report(47); + report(expr, 47); continue; } - const auto& v = expr->val(); - if (v.ident != iCONSTEXPR) { - report(8); + auto val = sema_->CheckRvalue(expr); + if (!val) + return nullptr; + + auto cv = val->as(); + if (!cv) { + report(expr, 8); continue; } - matchtag(rank->inner(), v.type(), MATCHTAG_COERCE); + matchtag(rank->inner(), val->type().ptr(), MATCHTAG_COERCE); prev2 = prev1; - prev1 = ke::Some(v.constval()); + prev1 = ke::Some(cv->value()); + + values.emplace_back(cv); } cell ncells = rank_size ? rank_size : array->exprs().size(); if (!AddCells(ncells)) - return false; + return nullptr; if (array->ellipses()) { if (array->exprs().empty()) { // Invalid ellipses, array size unknown. report(array->pos(), 41); - return true; + return nullptr; } if (prev1.isValid() && prev2.isValid() && !rank->inner()->isInt()) { // Unknown stepping type. report(array->exprs().back()->pos(), 68) << rank->inner(); - return false; + return nullptr; } if (!rank_size || (rank_size == (cell)array->exprs().size() && !array->synthesized_for_compat())) { // Initialization data exceeds declared size. report(array->exprs().back()->pos(), 18); - return false; + return nullptr; } } - return true; + + return new ir::ArrayInitializer(array, QualType(rank), std::move(values)); } -bool ArrayValidator::ValidateEnumStruct(EnumStructDecl* es, Expr* init) { +ir::Value* ArrayValidator::ValidateEnumStruct(EnumStructDecl* es, Expr* init) { ArrayExpr* array = init->as(); if (!array) { report(init->pos(), 47); - return false; + return nullptr; } const auto& field_list = es->fields(); auto field_iter = field_list.begin(); + std::vector values; for (const auto& expr : array->exprs()) { if (field_iter == field_list.end()) { report(expr->pos(), 91); - return false; + return nullptr; } auto field = (*field_iter); @@ -729,29 +741,33 @@ bool ArrayValidator::ValidateEnumStruct(EnumStructDecl* es, Expr* init) { const auto& type = field->type_info(); if (type.type->isArray()) { - if (!CheckArrayInitialization(sema_, type, expr)) - continue; + ir::Value* val; + if (!CheckArrayInitialization(sema_, type, expr, &val)) + return nullptr; + + assert(val); + values.emplace_back(val); } else { AutoErrorPos pos(expr->pos()); - if (!sema_->CheckExpr(expr)) - continue; + auto val = sema_->CheckRvalue(expr); + if (!val) + return nullptr; - const auto& v = expr->val(); - if (v.ident != iCONSTEXPR) { - report(8); - continue; + if (!val->as()) { + report(expr, 8); + return nullptr; } - matchtag(type.type, v.type(), MATCHTAG_COERCE | MATCHTAG_ENUM_ASSN); + matchtag(type.type, val->type().ptr(), MATCHTAG_COERCE | MATCHTAG_ENUM_ASSN); } } if (array->ellipses()) { report(array->pos(), 80); - return false; + return nullptr; } - return true; + return new ir::ArrayInitializer(array, QualType(es->type()), std::move(values)); } bool ArrayValidator::AddCells(size_t ncells) { @@ -768,7 +784,7 @@ bool ArrayValidator::AddCells(size_t ncells) { return true; } -bool Semantics::AddImplicitDynamicInitializer(VarDeclBase* decl) { +ir::Value* Semantics::BuildImplicitDynamicInitializer(VarDeclBase* decl) { // Enum structs should be impossible here. typeinfo_t* type = decl->mutable_type_info(); assert(!type->type->asEnumStruct()); @@ -783,46 +799,54 @@ bool Semantics::AddImplicitDynamicInitializer(VarDeclBase* decl) { // // Note that these declarations use old tag-based syntax, and therefore // do not work with enum structs, which create implicit dimensions. - TypenameInfo ti = type->ToTypenameInfo(); std::vector exprs; for (size_t i = 0; i < type->dim_exprs.size(); i++) { Expr* expr = type->dim_exprs[i]; if (!expr) { report(decl->pos(), 184); - return false; + return nullptr; } exprs.emplace_back(expr); } assert(!decl->init_rhs()); + assert(false); +#if 0 + TypenameInfo ti = type->ToTypenameInfo(); auto init = new NewArrayExpr(decl->pos(), ti, exprs); - decl->set_init(init); if (!decl->autozero()) init->set_no_autozero(); - return true; + return init; +#endif + return nullptr; } -bool Semantics::CheckArrayDeclaration(VarDeclBase* decl) { +bool Semantics::CheckArrayDeclaration(VarDeclBase* decl, ir::Value** new_init) { AutoCountErrors errors; if (decl->implicit_dynamic_array()) { + assert(false); assert(!decl->init_rhs()); - if (!AddImplicitDynamicInitializer(decl)) - return false; + *new_init = BuildImplicitDynamicInitializer(decl); + return *new_init != nullptr; } ArrayValidator validator(this, decl); - if (!validator.Validate() || !errors.ok()) + if (!validator.Validate(new_init)) { + assert(!errors.ok()); return false; + } + + assert(errors.ok()); return true; } class CompoundEmitter final { public: - CompoundEmitter(Type* type, Expr* init) + CompoundEmitter(QualType type, ir::Value* init) : type_(type), init_(init), pending_zeroes_(0) @@ -847,17 +871,17 @@ class CompoundEmitter final private: static const int kDataFlag = 0x80000000; - cell Emit(ArrayType* type, Expr* expr); + cell Emit(ArrayType* type, ir::Value* expr); - 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, + size_t AddString(ir::CharArrayLiteral* init); + void AddInlineArray(LayoutFieldDecl* field, ir::ArrayInitializer* init); + void AddInlineEnumStruct(EnumStructDecl* es, ir::ArrayInitializer* init); + void EmitPadding(size_t rank_size, QualType type, size_t emitted, bool ellipses, const ke::Maybe prev1, const ke::Maybe prev2); private: - Type* type_; - Expr* init_; + QualType type_; + ir::Value* init_; tr::vector iv_; tr::vector data_; size_t pending_zeroes_; @@ -866,7 +890,7 @@ class CompoundEmitter final void CompoundEmitter::Emit() { if (auto es = type_->asEnumStruct()) { if (init_) - AddInlineEnumStruct(es, init_->as()); + AddInlineEnumStruct(es, init_->to()); EmitPadding(1, type_, data_size(), false, {}, {}); } else { Emit(type_->to(), init_); @@ -883,7 +907,7 @@ void CompoundEmitter::Emit() { } } -cell CompoundEmitter::Emit(ArrayType* rank, Expr* init) { +cell CompoundEmitter::Emit(ArrayType* rank, ir::Value* init) { if (rank->inner()->isArray()) { assert(rank->size()); @@ -892,13 +916,13 @@ cell CompoundEmitter::Emit(ArrayType* rank, Expr* init) { iv_.resize(start + rank->size()); - ArrayExpr* array = init ? init->as() : nullptr; - assert(!array || (array->exprs().size() == size_t(rank->size()))); + auto array = init ? init->as() : nullptr; + assert(!array || (array->values().size() == size_t(rank->size()))); // :TODO: test when sizeof(array) < sizeof(rank) auto inner = rank->inner()->to(); for (int i = 0; i < rank->size(); i++) { - Expr* child = array ? array->exprs().at(i) : nullptr; + auto child = array ? array->values().at(i) : nullptr; // Note: use a temporary to store the result of Emit(), since // the address of iv_[start+i] could be evaluated and cached, @@ -916,23 +940,23 @@ cell CompoundEmitter::Emit(ArrayType* rank, Expr* init) { ke::Maybe prev1, prev2; if (!init) { assert(rank->size()); - } else if (ArrayExpr* array = init->as()) { - for (const auto& item : array->exprs()) { - if (ArrayExpr* expr = item->as()) { + } else if (auto array = init->as()) { + for (const auto& item : array->values()) { + if (auto inner = item->as()) { // Subarrays can only appear in an enum struct. Normal 2D cases // would flow through the check at the start of this function. auto es = rank->inner()->asEnumStruct(); - AddInlineEnumStruct(es, expr); + AddInlineEnumStruct(es, inner); } else { - assert(item->val().ident == iCONSTEXPR); - add_data(item->val().constval()); + auto cv = item->as(); + add_data(cv->value()); prev2 = prev1; - prev1 = ke::Some(item->val().constval()); + prev1 = ke::Some(cv->value()); } } - ellipses = array->ellipses(); - } else if (StringExpr* expr = init->as()) { - AddString(expr); + ellipses = array->expr()->ellipses(); + } else if (auto inner = init->as()) { + AddString(inner); } else { assert(false); } @@ -948,34 +972,34 @@ 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; } -void CompoundEmitter::AddInlineEnumStruct(EnumStructDecl* es, ArrayExpr* array) { +void CompoundEmitter::AddInlineEnumStruct(EnumStructDecl* es, ir::ArrayInitializer* init) { auto field_list = &es->fields(); auto field_iter = field_list->begin(); - for (const auto& item : array->exprs()) { - if (StringExpr* expr = item->as()) { + for (const auto& item : init->values()) { + if (auto inner = item->as()) { // Substrings can only appear in an enum struct. Normal 2D // cases would flow through the outer string check. - size_t emitted = AddString(expr); + size_t emitted = AddString(inner); auto field = (*field_iter); assert(field); auto rank_type = field->type()->to(); - EmitPadding(rank_type->size(), rank_type->inner(), emitted, false, {}, {}); - } else if (ArrayExpr* expr = item->as()) { + EmitPadding(rank_type->size(), QualType(rank_type->inner()), emitted, false, {}, {}); + } else if (auto inner = item->as()) { // Subarrays can only appear in an enum struct. Normal 2D cases // would flow through the check at the start of this function. auto field = (*field_iter); - AddInlineArray(field, expr); + AddInlineArray(field, inner); } else { - assert(item->val().ident == iCONSTEXPR); - add_data(item->val().constval()); + auto cv = item->as(); + add_data(cv->value()); } assert(field_iter != field_list->end()); @@ -983,23 +1007,23 @@ void CompoundEmitter::AddInlineEnumStruct(EnumStructDecl* es, ArrayExpr* array) } } -void CompoundEmitter::AddInlineArray(LayoutFieldDecl* field, ArrayExpr* array) { +void CompoundEmitter::AddInlineArray(LayoutFieldDecl* field, ir::ArrayInitializer* array) { ke::Maybe prev1, prev2; - for (const auto& item : array->exprs()) { - assert(item->val().ident == iCONSTEXPR); - add_data(item->val().constval()); + for (const auto& item : array->values()) { + auto cv = item->as(); + add_data(cv->value()); prev2 = prev1; - prev1 = ke::Some(item->val().constval()); + prev1 = ke::Some(cv->value()); } auto rank_size = field->type()->to()->size(); - EmitPadding(rank_size, field->type(), array->exprs().size(), - array->ellipses(), prev1, prev2); + EmitPadding(rank_size, QualType(field->type()), array->values().size(), + array->expr()->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. @@ -1026,11 +1050,9 @@ CompoundEmitter::EmitPadding(size_t rank_size, Type* type, size_t emitted, bool } } -size_t -CompoundEmitter::AddString(StringExpr* expr) -{ +size_t CompoundEmitter::AddString(ir::CharArrayLiteral* init) { std::vector out; - litadd_str(expr->text()->chars(), expr->text()->length(), &out); + litadd_str(init->expr()->text()->chars(), init->expr()->text()->length(), &out); for (const auto& val : out) add_data(val); @@ -1053,20 +1075,20 @@ CompoundEmitter::add_data(cell value) data_.emplace_back(value); } -void BuildCompoundInitializer(Type* type, Expr* init, ArrayData* array) { +void BuildCompoundInitializer(QualType type, ir::Value* 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..9941180b3 100644 --- a/compiler/array-helpers.h +++ b/compiler/array-helpers.h @@ -20,7 +20,10 @@ // 3. This notice may not be removed or altered from any source distribution. #pragma once +#include + #include "array-data.h" +#include "ir.h" #include "parse-node.h" namespace sp { @@ -36,10 +39,14 @@ bool ResolveArrayType(Semantics* sema, VarDeclBase* decl); bool ResolveArrayType(Semantics* sema, const token_pos_t& pos, typeinfo_t* type, int vclass); // Perform type and size checks of an array and its initializer if present. -bool CheckArrayInitialization(Semantics* sema, const typeinfo_t& type, Expr* init); +// +// Returns a new initializer (or null if no initializer was present) on success. +// Returns std::nullopt on failure. +bool CheckArrayInitialization(Semantics* sema, const typeinfo_t& type, Expr* init, + ir::Value** new_init); -void BuildCompoundInitializer(VarDeclBase* decl, ArrayData* array, cell_t base_addr); -void BuildCompoundInitializer(Type* type, Expr* init, ArrayData* array); +void BuildCompoundInitializer(QualType type, ir::Value* 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..74de1e830 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,55 @@ FOR_EACH(StructExpr) \ FOR_EACH(StructInitFieldExpr) +#define IR_NODE_TYPE_LIST(FOR_EACH) \ + /* Decls */ \ + FOR_EACH(Function) \ + FOR_EACH(Variable) \ + FOR_EACH(Argument) \ + /* 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(PropertyRef) \ + FOR_EACH(FieldRef) \ + FOR_EACH(UnaryOp) \ + FOR_EACH(CallUserOp) \ + FOR_EACH(Store) \ + FOR_EACH(StackRef) \ + FOR_EACH(AddressOf) \ + FOR_EACH(ArrayInitializer) \ + FOR_EACH(StoreWithTemp) \ + FOR_EACH(IncDec) \ + FOR_EACH(IncDecWithTemp) \ + FOR_EACH(TempValueRef) \ + FOR_EACH(BoundFunction) \ + FOR_EACH(CastOp) + +namespace sp { +namespace cc { + enum class ExprKind : uint8_t { #define _(Name) Name, @@ -92,3 +143,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..4e6eef867 100644 --- a/compiler/code-generator.cpp +++ b/compiler/code-generator.cpp @@ -24,6 +24,7 @@ #include #include +#include #include "array-helpers.h" #include "assembler.h" @@ -41,9 +42,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 +52,12 @@ bool CodeGenerator::Generate() { // First instruction is always halt. __ emit(OP_HALT, 0); - EmitStmtList(tree_->stmts()); + for (const auto& var : mod_->globals()) + EmitGlobalVar(var); + + for (const auto& fun : mod_->functions()) + EmitFunction(fun); + if (!ComputeStackUsage()) return false; @@ -76,6 +82,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 +92,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 +114,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 +132,64 @@ void CodeGenerator::AddDebugSymbols(tr::vector* list) { } } +void CodeGenerator::EmitBlock(ir::InsnBlock* block) { + if (block->has_heap_allocs()) + EnterHeapScope(Flow_None); + + for (auto iter = block->list(); iter; iter = iter->next()) + EmitInsn(iter); + + if (block->has_heap_allocs()) + LeaveHeapScope(); +} + +void CodeGenerator::EmitInsn(ir::Insn* node) { + switch (node->kind()) { + case IrKind::Return: + EmitReturn(node->to()); + break; + case IrKind::Variable: + EmitVarDecl(node->to()); + break; + case IrKind::ValueInsn: + EmitValueInsn(node->to()); + break; + case IrKind::DoWhile: + EmitDoWhile(node->to()); + break; + case IrKind::If: + EmitIf(node->to()); + break; + case IrKind::Break: + EmitLoopControl(tBREAK); + break; + case IrKind::Continue: + EmitLoopControl(tCONTINUE); + break; + case IrKind::Switch: + EmitSwitch(node->to()); + break; + case IrKind::ForLoop: + EmitForLoop(node->to()); + break; + case IrKind::Delete: + EmitDelete(node->to()); + 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 +306,7 @@ CodeGenerator::EmitStmt(Stmt* stmt) if (stmt->tree_has_heap_allocs()) LeaveHeapScope(); } +#endif void CodeGenerator::EmitChangeScopeNode(ChangeScopeNode* node) @@ -269,30 +338,38 @@ 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 } -void CodeGenerator::EmitGlobalVar(VarDeclBase* decl) { - BinaryExpr* init = decl->init(); +void CodeGenerator::EmitGlobalVar(ir::Variable* var) { + auto decl = var->decl(); + auto init = var->init(); - __ bind_to(decl->label(), data_.dat_address()); + var->set_addr(data_.dat_address()); if (decl->type()->isArray() || (decl->ident() == iVARIABLE && decl->type()->isEnumStruct())) { ArrayData array; - BuildCompoundInitializer(decl, &array, data_.dat_address()); + BuildCompoundInitializer(QualType(decl->type()), var->init(), &array, data_.dat_address()); data_.Add(std::move(array.iv)); data_.Add(std::move(array.data)); @@ -302,39 +379,31 @@ void CodeGenerator::EmitGlobalVar(VarDeclBase* decl) { if (auto es = decl->type()->asEnumStruct()) cells = es->array_size(); - // TODO initialize ES - assert(!init || init->right()->val().ident == iCONSTEXPR); - if (init) - data_.Add(init->right()->val().constval()); - else + if (init) { + auto cv = init->to(); + data_.Add(cv->value()); + } else { data_.AddZeroes(cells); + } } else { assert(false); } } -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 +413,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()), var->init(), &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 +453,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 +468,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 +513,213 @@ 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; + // l-values must be either handled by stores or loads. + assert(!val->as()); + + switch (val->kind()) { + case IrKind::ConstVal: + EmitConst(val->to()); + break; + case IrKind::UnaryOp: + EmitUnary(val->to()); + break; + case IrKind::Load: + EmitLoad(val->to()); + break; + case IrKind::CommaOp: + EmitCommaOp(val->to()); + break; + case IrKind::CallOp: + EmitCallOp(val->to()); + break; + case IrKind::BinaryOp: + EmitBinary(val->to()); + break; + case IrKind::CharArrayLiteral: + EmitCharArrayLiteral(val->to()); + break; + case IrKind::TernaryOp: + EmitTernaryOp(val->to()); + break; + case IrKind::Store: + EmitStore(val->to()); + break; + case IrKind::StoreWithTemp: + EmitStoreWithTemp(val->to()); + break; + case IrKind::FunctionRef: + EmitFunctionRef(val->to()); + break; + case IrKind::AddressOf: + EmitAddressOf(val->to()); + break; + case IrKind::CallUserOp: + EmitCallUserOp(val->to()); + break; + case IrKind::IncDec: + case IrKind::IncDecWithTemp: + EmitIncDec(val->to()); + break; + case IrKind::TempValueRef: + EmitTempValueRef(val->to()); + break; + case IrKind::CastOp: { + auto inner = val->to()->val(); + EmitValue(inner); + break; + } + default: + assert(false); + break; } +} - switch (expr->kind()) { - case ExprKind::UnaryExpr: - EmitUnary(expr->to()); +void CodeGenerator::EmitLoad(ir::Load* load) { + auto lval = load->lval(); + EmitLoadStorePrologue(lval); + EmitLoadEpilogue(lval); +} + +void CodeGenerator::EmitAddressOf(ir::AddressOf* op) { + auto lval = op->lval(); + switch (lval->kind()) { + case IrKind::VariableRef: { + auto ref = lval->as(); + auto var = ref->var(); + auto vclass = var->decl()->vclass(); + if (vclass == sSTATIC || vclass == sGLOBAL) + __ const_pri(var->addr()); + else if (ref->type()->isReference()) + __ emit(OP_LOAD_S_PRI, var->addr()); + else + __ emit(OP_ADDR_PRI, var->addr()); break; + } + case IrKind::IndexOp: { + auto index = lval->to(); + EmitAddressOfIndexOp(index); + + if (index->type()->isArray()) + __ emit(OP_LOAD_I); + break; + } + case IrKind::StackRef: { + auto op = lval->to(); + __ emit(OP_ADDR_PRI, GetSlotAddr(op->slot())); + break; + } + default: + assert(false); + } +} + +void CodeGenerator::EmitTempValueRef(ir::TempValueRef* ref) { + uint32_t slot_addr = GetSlotAddr(ref->slot()); + EmitValue(ref->val()); + __ emit(OP_STOR_S_PRI, slot_addr); + __ emit(OP_ADDR_PRI, slot_addr); +} + +void CodeGenerator::EmitStore(ir::Store* op) { + auto lval = op->lval(); + bool save_pri = EmitLoadStorePrologue(lval); + if (save_pri) + __ emit(OP_PUSH_PRI); + + EmitValue(op->val()); + + if (save_pri) + __ emit(OP_POP_ALT); + + EmitStoreEpilogue(lval, true /* save_rval */); +} + +void CodeGenerator::EmitStoreWithTemp(ir::StoreWithTemp* op) { + auto lval = op->lval(); + cell_t temp_addr = GetSlotAddr(op->temp_slot()); + + [[maybe_unused]] bool save_pri = EmitLoadStorePrologue(lval); + assert(save_pri); + + // Save the lval address, then dereference it, and store the value in the + // temporary stack location. This will get used by the Load(StackRef) in + // the right-hand side of the store. + __ emit(OP_PUSH_PRI); + EmitLoadEpilogue(lval); + __ emit(OP_STOR_S_PRI, temp_addr); + EmitValue(op->val()); + __ emit(OP_POP_ALT); + EmitStoreEpilogue(lval, true /* save_rval */); +} + +void CodeGenerator::EmitIncDec(ir::IncDec* incdec) { + auto lval = incdec->lval(); + bool prefix = incdec->expr()->prefix(); + bool used = incdec->used(); + + // If the postfix value isn't used, it can be CG'd as a prefix operation. + bool postfix = used && !prefix; + + bool save_pri = EmitLoadStorePrologue(lval); + if (save_pri) + __ emit(OP_PUSH_PRI); + EmitLoadEpilogue(lval); + // We might need to initialize a temporary slot with the pre-incremented + // value, if the increment operator was overloaded. + if (auto with_temp = incdec->as()) + __ emit(OP_STOR_S_PRI, GetSlotAddr(with_temp->slot())); + if (save_pri) + __ emit(OP_POP_ALT); + if (postfix) + __ emit(OP_PUSH_PRI); + if (incdec->expr()->token() == tINC) + __ emit(OP_ADD_C, 1); + else + __ emit(OP_ADD_C, -1); + // Calling setters might return a bogus PRI. Make sure to save it if we + // need the prefix value. + EmitStoreEpilogue(lval, prefix && used /* save_rval */); + if (postfix) + __ emit(OP_POP_PRI); +} + +void CodeGenerator::EmitCallOp(ir::CallOp* call) { + auto target = call->target(); + assert(!target->fun()->decl()->return_array()); + + const auto& argv = call->argv(); + cell_t nargs = 0; + for (size_t i = argv.size() - 1; i < argv.size(); i--) { + EmitValue(argv[i]); + __ emit(OP_PUSH_PRI); + nargs++; + } + + if (auto bound = target->as()) { + EmitValue(bound->val()); + __ emit(OP_PUSH_PRI); + nargs++; + } + + EmitCall(target->fun(), nargs); + + assert(!target->fun()->decl()->return_array()); +} + +void CodeGenerator::EmitFunctionRef(ir::FunctionRef* ref) { + __ emit(OP_CONST_PRI, &ref->fun()->public_id()); +} + +#if 0 +void +CodeGenerator::EmitExpr(Expr* expr) +{ + switch (expr->kind()) { case ExprKind::IncDecExpr: EmitIncDec(expr->to()); break; @@ -526,40 +794,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 +843,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,292 +861,325 @@ 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); - } else { - if (expr->token() == tINC) - EmitInc(&tmp); /* increase variable first */ - else - EmitDec(&tmp); - } - 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); - } - __ emit(OP_POP_ALT); - InvokeSetter(val.accessor(), TRUE); - } + 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 { - if (val.ident == iARRAYCELL || val.ident == iARRAYCHAR || val.ident == iACCESSOR) { - // Save base address. Stack: [addr] - __ emit(OP_PUSH_PRI); - // Get pre-inc value. - EmitRvalue(val); - // Save pre-inc value, but swap its position with the address. - __ emit(OP_POP_ALT); // Stack: [] - __ emit(OP_PUSH_PRI); // Stack: [val] - if (userop.sym) { - __ emit(OP_PUSH_ALT); // Stack: [val addr] - // Call the overload. + EmitValue(op->left()); + + if (auto right_cv = op->right()->as()) { + if (IsOperTokenCommutative(op->token())) { + __ const_alt(right_cv->value()); + } else { __ emit(OP_PUSH_PRI); - EmitCall(userop.sym, 1); - // Restore the address and emit the store. + __ const_pri(right_cv->value()); __ emit(OP_POP_ALT); - EmitStore(&val); - } else { - if (val.ident != iACCESSOR) - __ emit(OP_MOVE_PRI); - if (expr->token() == tINC) - EmitInc(&val); - else - EmitDec(&val); } - __ emit(OP_POP_PRI); } else { - // Much simpler case when we don't have to save the base address. - EmitRvalue(val); __ emit(OP_PUSH_PRI); - if (userop.sym) { - __ emit(OP_PUSH_PRI); - EmitCall(userop.sym, 1); - EmitStore(&val); - } else { - if (expr->token() == tINC) - EmitInc(&val); - else - EmitDec(&val); - } - __ emit(OP_POP_PRI); + EmitValue(op->right()); + __ emit(OP_POP_ALT); } } + + 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::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); +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); +} + +bool CodeGenerator::EmitLoadStorePrologue(ir::Lvalue* lval) { + switch (lval->kind()) { + case IrKind::VariableRef: + return false; + + case IrKind::IndexOp: + EmitAddressOfIndexOp(lval->to()); + return true; + + // StackRef can have a convenience initializer. + case IrKind::StackRef: + return false; + + case IrKind::FieldRef: + EmitAddressOfFieldRef(lval->to()); + return true; + + case IrKind::PropertyRef: { + auto ref = lval->to(); + EmitValue(ref->val()); + return true; } - if (expr->array_copy_length()) { - assert(!oper); - assert(!expr->assignop().sym); + default: + assert(false); + return true; + } +} +void CodeGenerator::EmitLoadEpilogue(ir::Lvalue* lval) { + switch (lval->kind()) { + case IrKind::VariableRef: + EmitLoadVariable(lval->to()); + break; + case IrKind::IndexOp: { + auto op = lval->to(); + if (op->base()->type()->isCharArray()) + __ emit(OP_LODB_I, 1); + else + __ emit(OP_LOAD_I); + break; + } + case IrKind::StackRef: { + auto ref = lval->to(); + __ emit(OP_LOAD_S_PRI, GetSlotAddr(ref->slot())); + break; + } + case IrKind::FieldRef: + __ emit(OP_LOAD_I); + break; + case IrKind::PropertyRef: { + auto ref = lval->to(); + assert(ref->getter()); __ emit(OP_PUSH_PRI); - EmitExpr(right); - __ emit(OP_POP_ALT); - __ emit(OP_MOVS, expr->array_copy_length() * sizeof(cell)); - return; + EmitCall(ref->getter(), 1); + break; } + default: + assert(false); } +} - assert(!expr->array_copy_length()); - assert(!left_val.type()->isArray()); +void CodeGenerator::EmitLoadVariable(ir::VariableRef* ref) { + auto var = ref->var(); + auto vclass = var->decl()->vclass(); + if (vclass == sLOCAL || vclass == sARGUMENT) { + if (ref->type()->isReference()) + __ emit(OP_LREF_S_PRI, var->addr()); + else + __ emit(OP_LOAD_S_PRI, var->addr()); + } else if (ref->type()->isArray() || ref->type()->isEnumStruct()) { + __ emit(OP_CONST_PRI, var->addr()); + } else { + __ emit(OP_LOAD_PRI, var->addr()); + } +} - EmitBinaryInner(oper, expr->userop(), left, right); +void CodeGenerator::EmitAddressOfIndexOp(ir::IndexOp* op) { + EmitValue(op->base()); - if (IsAssignOp(token)) { - if (saved_lhs) - __ emit(OP_POP_ALT); + cell_t rank_size = sizeof(cell_t); - auto tmp = left_val; - if (expr->assignop().sym) - EmitUserOp(expr->assignop(), nullptr); - EmitStore(&tmp); + auto array_type = op->base()->type()->as(); + if (!array_type->inner()->isArray()) { + if (array_type->inner()->isChar()) + rank_size = 1; + else if (auto es = array_type->inner()->asEnumStruct()) + rank_size = es->array_size() * sizeof(cell_t); + } + + assert(rank_size == 1 || (rank_size % sizeof(cell_t) == 0)); + + if (auto cv = op->index()->as()) { + if (cv->value() != 0) + __ emit(OP_ADD_C, cv->value() * rank_size); + } else { + __ emit(OP_PUSH_PRI); + EmitValue(op->index()); + + if (array_type->size()) { + __ emit(OP_BOUNDS, array_type->size() - 1); /* run time check for array bounds */ + } else { + // vm uses unsigned compare, this protects against negative indices. + __ emit(OP_BOUNDS, INT_MAX); + } + + if (rank_size == 1) { + __ emit(OP_POP_ALT); + __ emit(OP_ADD); + } else { + __ emit(OP_POP_ALT); + if (rank_size != sizeof(cell_t)) + __ emit(OP_SMUL_C, rank_size / sizeof(cell_t)); + __ emit(OP_IDXADDR); + } } } -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::EmitAddressOfFieldRef(ir::FieldRef* ref) { + EmitValue(ref->base()); + + if (ref->field()->offset()) { + __ const_alt(ref->field()->offset() * sizeof(cell_t)); + __ emit(OP_ADD); + } +} - 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; +void CodeGenerator::EmitStoreEpilogue(ir::Lvalue* lval, bool save_rval) { + switch (lval->kind()) { + case IrKind::VariableRef: { + auto ref = lval->to(); + auto var = ref->var(); + auto decl = var->decl(); + if (decl->vclass() == sSTATIC || decl->vclass() == sGLOBAL) { + __ emit(OP_STOR_PRI, var->addr()); + } else if (decl->type()->isReference()) { + __ emit(OP_SREF_S_PRI, var->addr()); } else { - if (must_save_lhs) - __ emit(OP_PUSH_PRI); - __ const_pri(right_val.constval()); - if (must_save_lhs) - __ emit(OP_POP_ALT); + __ emit(OP_STOR_S_PRI, var->addr()); } - } else { - if (must_save_lhs) - __ emit(OP_PUSH_PRI); - EmitExpr(right); - if (must_save_lhs) - __ emit(OP_POP_ALT); + break; } - } - if (oper_tok) { - if (user_op.sym) { - EmitUserOp(user_op, nullptr); - return; + case IrKind::IndexOp: { + auto op = lval->to(); + if (op->base()->type()->isCharArray()) + __ emit(OP_STRB_I, 1); + else + __ emit(OP_STOR_I); + break; } - 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); + + case IrKind::FieldRef: + __ emit(OP_STOR_I); + break; + + case IrKind::PropertyRef: { + auto ref = lval->to(); + assert(ref->setter()); + + __ emit(OP_PUSH_PRI); + __ emit(OP_PUSH_ALT); + EmitCall(ref->setter(), 2); + 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); +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; + } + } + out->emplace_back(op); } -void -CodeGenerator::EmitLogicalExprTest(LogicalExpr* root, bool jump_on_true, Label* target) -{ - std::vector sequence; - root->FlattenLogical(root->token(), &sequence); +void CodeGenerator::EmitLogicalExprTest(ir::BinaryOp* root, bool jump_on_true, sp::Label* target) { + assert(root->token() == tlAND || root->token() == tlOR); + + std::vector sequence; + FlattenLogical(root->left(), root->token(), &sequence); + FlattenLogical(root->right(), root->token(), &sequence); // a || b || c .... given jumpOnTrue, should be: // @@ -923,26 +1225,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 +1262,9 @@ CmpTokenToOp(int token) return OP_HALT; } } +#endif +#if 0 bool CodeGenerator::EmitChainedCompareExprTest(ChainedCompareExpr* root, bool jump_on_true, Label* target) @@ -993,150 +1297,63 @@ CodeGenerator::EmitChainedCompareExprTest(ChainedCompareExpr* root, bool jump_on case tlLE: token = '>'; break; - default: - assert(false); - } - } - __ emit(CmpTokenToOp(token), target); - return true; -} - -void -CodeGenerator::EmitChainedCompareExpr(ChainedCompareExpr* root) -{ - EmitExpr(root->first()); - - Expr* left = root->first(); - - int count = 0; - for (const auto& op : root->ops()) { - // EmitInner() guarantees the right-hand side will be preserved in ALT. - // EmitUserOp implicitly guarantees this, as do os_less etc which - // use XCHG to swap the LHS/RHS expressions. - if (count) - __ relop_prefix(); - EmitBinaryInner(op.oper_tok, op.userop, left, op.expr); - if (count) - __ relop_suffix(); - - left = op.expr; - count++; - } -} - -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) -{ - Decl* sym = expr->decl(); - switch (sym->ident()) { - case iFUNCTN: { - auto fun = sym->as(); - assert(fun == fun->canonical()); - - assert(!fun->is_native()); - assert(fun->is_live()); - __ emit(OP_CONST_PRI, &fun->cg()->funcid); - break; - } - case iVARIABLE: - if (sym->type()->isArray() || sym->type()->isEnumStruct()) - __ address(sym, sPRI); - break; - default: - // Note: constexprs are handled in Expr::Emit(). - assert(false); - } -} - -void -CodeGenerator::EmitIndexExpr(IndexExpr* expr) -{ - EmitExpr(expr->base()); - - auto& base_val = expr->base()->val(); - - cell_t rank_size = sizeof(cell_t); - - auto array_type = base_val.type()->as(); - if (!array_type->inner()->isArray()) { - if (array_type->inner()->isChar()) - rank_size = 1; - else if (auto es = array_type->inner()->asEnumStruct()) - rank_size = es->array_size() * sizeof(cell_t); - } - - 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); - } else { - __ emit(OP_PUSH_PRI); - EmitExpr(expr->index()); - - if (array_type->size()) { - __ emit(OP_BOUNDS, array_type->size() - 1); /* run time check for array bounds */ - } else { - // vm uses unsigned compare, this protects against negative indices. - __ emit(OP_BOUNDS, INT_MAX); - } - - if (rank_size == 1) { - __ emit(OP_POP_ALT); - __ emit(OP_ADD); - } else { - __ emit(OP_POP_ALT); - if (rank_size != sizeof(cell_t)) - __ emit(OP_SMUL_C, rank_size / sizeof(cell_t)); - __ emit(OP_IDXADDR); + default: + assert(false); } } - - // The indexed item is another array (multi-dimensional arrays). - if (array_type->inner()->isArray()) { - assert(expr->val().type()->isArray()); - __ emit(OP_LOAD_I); - } + __ emit(CmpTokenToOp(token), target); + return true; } void -CodeGenerator::EmitFieldAccessExpr(FieldAccessExpr* expr) +CodeGenerator::EmitChainedCompareExpr(ChainedCompareExpr* root) { - assert(expr->token() == '.'); + EmitExpr(root->first()); - // 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()); + Expr* left = root->first(); - // Only enum struct accesses have a resolved decl. - if (!expr->resolved()) - return; + int count = 0; + for (const auto& op : root->ops()) { + // EmitInner() guarantees the right-hand side will be preserved in ALT. + // EmitUserOp implicitly guarantees this, as do os_less etc which + // use XCHG to swap the LHS/RHS expressions. + if (count) + __ relop_prefix(); + EmitBinaryInner(op.oper_tok, op.userop, left, op.expr); + if (count) + __ relop_suffix(); - if (LayoutFieldDecl* field = expr->resolved()->as()) { - if (field->offset()) { - __ const_alt(field->offset() << 2); - __ emit(OP_ADD); + left = op.expr; + count++; + } +} + +void +CodeGenerator::EmitSymbolExpr(SymbolExpr* expr) +{ + Decl* sym = expr->decl(); + switch (sym->ident()) { + case iFUNCTN: { + auto fun = sym->as(); + assert(fun == fun->canonical()); + + assert(!fun->is_native()); + assert(fun->is_live()); + __ emit(OP_CONST_PRI, &fun->cg()->funcid); + break; } + case iVARIABLE: + if (sym->type()->isArray() || sym->type()->isEnumStruct()) + __ address(sym, sPRI); + break; + default: + // Note: constexprs are handled in Expr::Emit(). + assert(false); } } +#endif +#if 0 void CodeGenerator::EmitCallExpr(CallExpr* call) { // If returning an array, push a hidden parameter. if (call->fun()->return_array()) { @@ -1231,21 +1448,62 @@ CodeGenerator::EmitDefaultArgExpr(DefaultArgExpr* expr) __ const_pri(arg->default_value()->val.get()); } } +#endif -void -CodeGenerator::EmitCallUserOpExpr(CallUserOpExpr* expr) -{ - EmitExpr(expr->expr()); +void CodeGenerator::EmitCallUserOp(ir::CallUserOp* op) { + auto opertok = op->target()->decl()->decl().opertok; + + switch (opertok) { + case tINC: + case tDEC: + assert(false); + break; + case tlLE: + case tlGE: + case '>': + case '<': + break; + case '=': + assert(false); + break; + case '~': + case '!': + break; + case '+': + case '-': + case '*': + case '/': + case '%': + case tlNE: + case tlEQ: + break; + default: + assert(false); + } + + EmitValue(op->first()); + __ emit(OP_PUSH_PRI); - const auto& userop = expr->userop(); - if (userop.oper) { - auto val = expr->expr()->val(); - EmitUserOp(userop, &val); + if (op->second()) { + assert(op->target()->argv().size() == 2); + + // Arguments are pushed in reverse order. + EmitValue(op->second()); + if (op->swapped()) { + __ emit(OP_PUSH_PRI); + } else { + // PRI = right, stack = [left]. Swap them. + __ emit(OP_SWAP_PRI); + // PRI = left, stack = [right]. Push left val. + __ emit(OP_PUSH_PRI); + } + EmitCall(op->target(), 2); } else { - EmitUserOp(userop, nullptr); + EmitCall(op->target(), 1); } } +#if 0 void CodeGenerator::EmitNewArrayExpr(NewArrayExpr* expr) { int numdim = 0; auto& exprs = expr->exprs(); @@ -1278,31 +1536,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 +1604,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,43 +1622,26 @@ CodeGenerator::EmitReturnStmt(ReturnStmt* stmt) __ emit(OP_RETN); } -void -CodeGenerator::EmitDeleteStmt(DeleteStmt* stmt) -{ - Expr* expr = stmt->expr(); - auto v = expr->val(); - - // Only zap non-const lvalues. - bool zap = expr->lvalue(); - if (zap && v.sym && v.sym->is_const()) - zap = false; - - EmitExpr(expr); - - bool popaddr = false; - MethodmapPropertyDecl* accessor = nullptr; - if (expr->lvalue()) { - if (zap) { - switch (v.ident) { - case iACCESSOR: - // EmitRvalue() removes iACCESSOR so we store it locally. - accessor = v.accessor(); - if (!accessor->setter()) { - zap = false; - break; - } - __ emit(OP_PUSH_PRI); - popaddr = true; - break; - case iARRAYCELL: - case iARRAYCHAR: - __ emit(OP_PUSH_PRI); - popaddr = true; - break; - } +void CodeGenerator::EmitDelete(ir::Delete* stmt) { + auto val = stmt->val(); + auto lval = val->as(); + if (lval) { + if (EmitLoadStorePrologue(lval)) { + __ emit(OP_PUSH_PRI); + EmitLoadEpilogue(lval); + __ emit(OP_POP_ALT); + // PRI = value, ALT = addr + } else { + EmitLoadEpilogue(lval); + // PRI = value } - - EmitRvalue(&v); + // Save PRI so we can pass it as |this| to our dtor. + __ emit(OP_PUSH_PRI); + __ emit(OP_ZERO_PRI); + EmitStoreEpilogue(lval, false /* save_rval */); + __ emit(OP_POP_PRI); + } else { + EmitValue(val); } // push.pri @@ -1412,21 +1649,10 @@ CodeGenerator::EmitDeleteStmt(DeleteStmt* stmt) // sysreq.c N 1 // stack 8 __ emit(OP_PUSH_PRI); - EmitCall(stmt->map()->dtor(), 1); - - if (zap) { - if (popaddr) - __ emit(OP_POP_ALT); - - // Store 0 back. - __ const_pri(0); - if (accessor) - InvokeSetter(accessor, FALSE); - else - EmitStore(&v); - } + EmitCall(stmt->dtor(), 1); } +#if 0 void CodeGenerator::EmitRvalue(value* lval) { @@ -1461,62 +1687,10 @@ CodeGenerator::EmitRvalue(value* lval) } } } +#endif -void -CodeGenerator::EmitStore(const value* lval) -{ - switch (lval->ident) { - case iARRAYCELL: - __ emit(OP_STOR_I); - break; - case iARRAYCHAR: - __ emit(OP_STRB_I, 1); - break; - case iACCESSOR: - InvokeSetter(lval->accessor(), true); - break; - case iVARIABLE: { - if (lval->type()->isReference()) { - auto var = lval->sym->as(); - assert(var->vclass() == sLOCAL || var->vclass() == sARGUMENT); - __ emit(OP_SREF_S_PRI, var->addr()); - break; - } - [[fallthrough]]; - } - default: { - auto var = lval->sym->as(); - if (var->vclass() == sLOCAL || var->vclass() == sARGUMENT) - __ emit(OP_STOR_S_PRI, var->addr()); - else - __ emit(OP_STOR_PRI, var->addr()); - break; - } - } -} - -void CodeGenerator::InvokeGetter(MethodmapPropertyDecl* prop) { - assert(prop->getter()); - - __ emit(OP_PUSH_PRI); - EmitCall(prop->getter(), 1); -} - -void CodeGenerator::InvokeSetter(MethodmapPropertyDecl* prop, bool save_pri) { - assert(prop->setter()); - - if (save_pri) - __ emit(OP_PUSH_PRI); - __ emit(OP_PUSH_PRI); - __ emit(OP_PUSH_ALT); - EmitCall(prop->setter(), 2); - if (save_pri) - __ emit(OP_POP_PRI); -} - -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 +1699,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 +1743,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,36 +1769,26 @@ CodeGenerator::EmitLoopControl(int token) __ emit(OP_JUMP, &loop_->continue_to); } -void -CodeGenerator::EmitForStmt(ForStmt* stmt) -{ +void CodeGenerator::EmitForLoop(ir::ForLoop* loop) { ke::Maybe debug_scope; + auto stmt = loop->stmt(); auto scope = stmt->scope(); if (scope) { pushstacklist(); debug_scope.init(this, &local_syms_); } - auto init = stmt->init(); - if (init) - EmitStmt(init); + if (auto init = loop->init()) + EmitBlock(init); LoopContext loop_cx; loop_cx.stack_scope_id = stack_scope_id(); loop_cx.heap_scope_id = heap_scope_id(); ke::SaveAndSet push_context(&loop_, &loop_cx); - auto body = stmt->body(); - bool body_always_exits = false; - if (body->flow_type() == Flow_Return || body->flow_type() == Flow_Break) { - if (!stmt->has_continue()) - body_always_exits = true; - } - - auto advance = stmt->advance(); - auto cond = stmt->cond(); - if (advance && !stmt->never_taken()) { + auto advance = loop->advance(); + if (advance) { // top: // // jf break @@ -1641,26 +1800,29 @@ CodeGenerator::EmitForStmt(ForStmt* stmt) Label top; __ bind(&top); - if (cond && !stmt->always_taken()) - EmitTest(cond, false, &loop_cx.break_to); + if (loop->cond()) + EmitTest(loop->cond(), false, &loop_cx.break_to); - EmitStmt(body); + EmitBlock(loop->body()); if (stmt->has_continue()) { __ bind(&loop_cx.continue_to); // It's a bit tricky to merge this into the same heap scope as // the statement, so we create a one-off scope. +#if 0 if (advance->tree_has_heap_allocs()) EnterHeapScope(Flow_None); +#endif - EmitExpr(advance); + EmitBlock(loop->advance()); +#if 0 if (advance->tree_has_heap_allocs()) LeaveHeapScope(); +#endif } - if (!body_always_exits) - __ emit(OP_JUMP, &top); + __ emit(OP_JUMP, &top); } else if (!stmt->never_taken()) { // continue: // @@ -1670,13 +1832,12 @@ CodeGenerator::EmitForStmt(ForStmt* stmt) // break: __ bind(&loop_cx.continue_to); - if (cond && !stmt->always_taken()) - EmitTest(cond, false, &loop_cx.break_to); + if (loop->cond()) + EmitTest(loop->cond(), false, &loop_cx.break_to); - EmitStmt(body); + EmitBlock(loop->body()); - if (!body_always_exits) - __ emit(OP_JUMP, &loop_cx.continue_to); + __ emit(OP_JUMP, &loop_cx.continue_to); } __ bind(&loop_cx.break_to); @@ -1686,10 +1847,8 @@ CodeGenerator::EmitForStmt(ForStmt* stmt) } } -void -CodeGenerator::EmitSwitchStmt(SwitchStmt* stmt) -{ - EmitExpr(stmt->expr()); +void CodeGenerator::EmitSwitch(ir::Switch* insn) { + EmitValue(insn->cond()); Label exit_label; Label table_label; @@ -1698,31 +1857,23 @@ CodeGenerator::EmitSwitchStmt(SwitchStmt* stmt) // Note: we use map for ordering so the case table is sorted. std::map case_labels; - for (const auto& case_entry : stmt->cases()) { - Stmt* stmt = case_entry.second; - + for (const auto& [labels, block] : insn->cases()) { Label label; __ bind(&label); - for (const auto& expr : case_entry.first) { - const auto& v = expr->val(); - assert(v.ident == iCONSTEXPR); - - case_labels.emplace(v.constval(), label); - } + for (const auto& label_value : labels) + case_labels.emplace(label_value, label); - EmitStmt(stmt); - if (!stmt->IsTerminal()) - __ emit(OP_JUMP, &exit_label); + EmitBlock(block); + __ emit(OP_JUMP, &exit_label); } Label default_label; Label* defcase = &exit_label; - if (stmt->default_case()) { + if (insn->default_case()) { __ bind(&default_label); - EmitStmt(stmt->default_case()); - if (!stmt->default_case()->IsTerminal()) - __ emit(OP_JUMP, &exit_label); + EmitBlock(insn->default_case()); + __ emit(OP_JUMP, &exit_label); defcase = &default_label; } @@ -1736,62 +1887,91 @@ CodeGenerator::EmitSwitchStmt(SwitchStmt* stmt) __ bind(&exit_label); } -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); + + // Minimum 16 cells for general slack. + current_memory_ = 16; + max_func_memory_ = current_memory_; - __ bind(&info->cg()->label); + __ 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_); + /* Stack layout: + * base + 0*sizeof(cell) == previous "base" + * base + 1*sizeof(cell) == function return address + * base + 2*sizeof(cell) == number of arguments + * base + 3*sizeof(cell) == first argument of the function + * So the offset of each argument is "(argcnt+3) * sizeof(cell)". + * + * Since arglist has an empty terminator at the end, we actually add 2. + */ + cell_t offset = 0; + for (const auto& arg : fun->argv()) { + arg->set_addr((offset + 3) * sizeof(cell_t)); + offset++; + } + +#if 0 for (const auto& fun_arg : info->args()) EnqueueDebugSymbol(fun_arg, asm_.position()); +#endif + + pushstacklist(); + if (fun->num_slots()) { + temp_slots_base_ = markstack(decl, MEMUSE_STATIC, fun->num_slots()); + __ emit(OP_STACK, -4 * cell_t(fun->num_slots())); + } else { + temp_slots_base_ = 0; + } + + EmitBlock(fun->body()); - EmitStmt(info->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 +1991,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 +2016,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,92 +2121,7 @@ CodeGenerator::EmitUserOp(const UserOperation& user_op, value* lval) } } } - -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(); - __ emit(OP_PUSH_PRI); - __ emit(OP_LREF_S_PRI, var->addr()); - __ emit(OP_INC_PRI); - __ emit(OP_SREF_S_PRI, var->addr()); - __ 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(); - __ emit(OP_PUSH_PRI); - __ emit(OP_LREF_S_PRI, var->addr()); - __ emit(OP_DEC_PRI); - __ emit(OP_SREF_S_PRI, var->addr()); - __ emit(OP_POP_PRI); - break; - } - [[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; - } - } -} +#endif void CodeGenerator::EnterMemoryScope(tr::vector& frame) @@ -2105,12 +2202,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 +2239,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 +2283,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 +2298,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 +2311,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 +2323,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; } @@ -2241,5 +2335,11 @@ bool CodeGenerator::ComputeStackUsage() { return ComputeStackUsage(callgraph_.begin()); } +cell_t CodeGenerator::GetSlotAddr(uint32_t slot) { + assert(slot < fun_->num_slots()); + assert(temp_slots_base_ != 0); + return temp_slots_base_ + slot * sizeof(cell_t); +} + } // namespace cc } // namespace sp diff --git a/compiler/code-generator.h b/compiler/code-generator.h index 479b4bca7..ce0c8dbad 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,58 +60,99 @@ class CodeGenerator final private: // Statements/decls. + void EmitFunction(ir::Function* fun); + void EmitBlock(ir::InsnBlock* block); + void EmitInsn(ir::Insn* node); + void EmitValueInsn(ir::ValueInsn* insn); + void EmitDoWhile(ir::DoWhile* loop); + void EmitIf(ir::If* insn); + void EmitSwitch(ir::Switch* 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 EmitGlobalVar(ir::Variable* var); + void EmitLocalVar(ir::Variable* var); void EmitIfStmt(IfStmt* stmt); - void EmitDeleteStmt(DeleteStmt* stmt); + void EmitDelete(ir::Delete* stmt); void EmitDoWhileStmt(DoWhileStmt* stmt); - void EmitForStmt(ForStmt* stmt); + void EmitForLoop(ir::ForLoop* loop); void EmitSwitchStmt(SwitchStmt* stmt); 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 EmitLoadVariable(ir::VariableRef* ref); + void EmitStore(ir::Store* op); + void EmitStoreWithTemp(ir::StoreWithTemp* op); + void EmitConst(ir::Const* cv); + void EmitCommaOp(ir::CommaOp* op); + void EmitCallOp(ir::CallOp* call); + void EmitAddressOf(ir::AddressOf* op); 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 EmitFunctionRef(ir::FunctionRef* ref); 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 EmitAddressOfIndexOp(ir::IndexOp* op); + void EmitAddressOfFieldRef(ir::FieldRef* op); void EmitFieldAccessExpr(FieldAccessExpr* expr); void EmitCallExpr(CallExpr* expr); void EmitDefaultArgExpr(DefaultArgExpr* expr); - void EmitCallUserOpExpr(CallUserOpExpr* expr); + void EmitCallUserOp(ir::CallUserOp* op); void EmitNewArrayExpr(NewArrayExpr* expr); + void EmitIncDec(ir::IncDec* incdec); + + // 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 no address was needed. + bool EmitLoadStorePrologue(ir::Lvalue* lval); + + // Emit a store. The value must be in PRI. If EmitLoadStorePrologue + // returned true, the address must be in ALT. + // + // If save_rval is true, it will be preserved against clobbers. + void EmitStoreEpilogue(ir::Lvalue* lval, bool save_rval); + + // Emit a load. If EmitLoadStorePrologue returned true, the address must be + // in PRI. + void EmitLoadEpilogue(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); void InvokeSetter(MethodmapPropertyDecl* method, bool save); void EmitRvalue(value* lval); - void EmitStore(const value* lval); void EmitBreak(); + void EmitTempValueRef(ir::TempValueRef* ref); void EmitRvalue(const value& lval) { value tmp = lval; @@ -177,7 +220,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,11 +239,13 @@ 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); + cell_t GetSlotAddr(uint32_t slot); + private: typedef tr::vector> SymbolStack; @@ -218,11 +263,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_; @@ -246,6 +292,7 @@ class CodeGenerator final int current_stack_ = 0; int current_memory_ = 0; int max_func_memory_ = 0; + int temp_slots_base_ = 0; CallGraph callgraph_; AutoCountErrors errors_; 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..91a298b22 100644 --- a/compiler/expressions.cpp +++ b/compiler/expressions.cpp @@ -40,174 +40,12 @@ 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())) +bool checktag_string(Type* type, Type* type2) { + if (type2->isArray()) 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; - - if ((sym1->type()->isChar() && type->isInt()) || - (sym1->type()->isInt() && type->isChar())) + if ((type2->isChar() && type->isInt()) || + (type2->isInt() && type->isChar())) { return true; } @@ -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..82b96db13 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); @@ -57,7 +53,7 @@ bool matchtag_commutative(int formaltag, int actualtag, int flags); bool matchtag_string(int ident, int tag); bool matchtag_string(int ident, Type* type); bool checkval_string(const value* sym1, const value* sym2); -bool checktag_string(Type* type, const value* sym1); +bool checktag_string(Type* type, Type* type2); bool checktag(Type* type, Type* expr_type); bool checktag_string(int tag, const value* sym1); bool checktag(int tag, int exprtag); diff --git a/compiler/ir.cpp b/compiler/ir.cpp new file mode 100644 index 000000000..3d184e406 --- /dev/null +++ b/compiler/ir.cpp @@ -0,0 +1,139 @@ +// 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::StoreWithTemp: + case IrKind::IncDec: + case IrKind::IncDecWithTemp: + case IrKind::CallOp: + return true; + case IrKind::PropertyRef: { + auto ir = to(); + return ir->val()->HasSideEffects(); + } + case IrKind::StackRef: + return false; + case IrKind::TempValueRef: { + auto ir = to(); + return ir->val()->HasSideEffects(); + } + case IrKind::FieldRef: { + auto ir = to(); + return ir->base()->HasSideEffects(); + } + case IrKind::IndexOp: { + auto ir = to(); + return ir->base()->HasSideEffects() || ir->index()->HasSideEffects(); + } + case IrKind::Load: { + auto ir = to(); + return ir->lval()->HasSideEffects(); + } + case IrKind::TernaryOp: { + auto ir = to(); + return ir->select()->HasSideEffects() || + ir->on_true()->HasSideEffects() || + ir->on_false()->HasSideEffects(); + } + case IrKind::BinaryOp: { + auto ir = to(); + return ir->left()->HasSideEffects() || + ir->right()->HasSideEffects(); + } + case IrKind::CommaOp: { + auto ir = to(); + for (const auto& val : ir->values()) { + if (val->HasSideEffects()) + return true; + } + return false; + } + case IrKind::Array: { + auto ir = to(); + for (const auto& val : ir->values()) { + if (val->HasSideEffects()) + return true; + } + return false; + } + case IrKind::UnaryOp: { + auto ir = to(); + return ir->val()->HasSideEffects(); + } + case IrKind::VariableRef: + case IrKind::ConstVal: + return false; + default: + assert(false); + 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); +} + +bool Lvalue::HasComplexAddressCalculation() { + assert(IsAddressable()); + + switch (kind()) { + case IrKind::VariableRef: + case IrKind::StackRef: + return false; + case IrKind::IndexOp: + case IrKind::FieldRef: + // FieldRef technically isn't complex in v1 ops, but it might be someday, if we + // introduce an equivalent to idxaddr for fields. + return true; + default: + assert(false); + return false; + } +} + +void PropertyRef::BindGetter(ir::Function* getter) { + assert(!getter_ || getter_ == getter); + getter_ = getter; +} + +void PropertyRef::BindSetter(ir::Function* setter) { + assert(!setter_ || setter_ == setter); + setter_ = setter; +} + +} // namespace ir +} // namespace cc +} // namespace sp diff --git a/compiler/ir.h b/compiler/ir.h new file mode 100644 index 000000000..94251d5a4 --- /dev/null +++ b/compiler/ir.h @@ -0,0 +1,990 @@ +// 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 +#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; + VarDecl* var_decl; + FunctionDecl* fun_decl; + StringExpr* string_expr; + DoWhileStmt* do_while_stmt; + ArgDecl* arg_decl; + ArrayExpr* array_expr; + ForStmt* for_stmt; + IncDecExpr* incdec_expr; + ThisExpr* this_expr; + } 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 Insn : public Node { + public: + Insn(IrKind kind, ParseNode* pn) + : Node(kind, pn) + {} + + Insn* next() const { return next_; } + void set_next(Insn* node) { next_ = node; } + + private: + Insn* next_ = nullptr; +}; + +class InsnBlock final : public PoolObject { + static constexpr uintptr_t kBits = 1; + + public: + explicit InsnBlock(Insn* list, bool has_heap_allocs) + : list_(list) + { + if (has_heap_allocs) + list_ = ke::SetPointerBits(list_, 1); + } + + Insn* list() const { return ke::ClearPointerBits(list_); } + bool has_heap_allocs() const { return ke::GetPointerBits(list_) == 1; } + + private: + Insn* list_; +}; + +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(Insn* 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; + } + + InsnBlock* Finish() { + auto block = new InsnBlock(first_, has_heap_allocs_); + first_ = nullptr; + last_ = nullptr; + has_heap_allocs_ = false; + return block; + } + + void set_has_heap_allocs() { has_heap_allocs_ = true; } + + NodeListBuilder& operator =(const NodeListBuilder) = delete; + NodeListBuilder& operator =(NodeListBuilder&&) = delete; + + private: + NodeListBuilder* prev_ = nullptr; + NodeListBuilder** prev_loc_ = nullptr; + Insn* first_ = nullptr; + Insn* last_ = nullptr; + bool has_heap_allocs_ = false; +}; + +class DeclNode : public Insn { + public: + DeclNode(IrKind kind, Decl* decl) + : Insn(kind, decl) + {} +}; + +class Variable : public DeclNode { + public: + Variable(VarDeclBase* var, ir::Value* init) + : DeclNode(IrKind::Variable, var), + init_(init), + read_(false), + written_(false) + {} + + 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_; } + bool read() const { return read_; } + bool written() const { return written_; } + + void set_read() { read_ = true; } + void set_written() { written_ = true; } + + static bool is_a(Node* node) { + return node->kind() == IrKind::Variable || node->kind() == IrKind::Argument; + } + + protected: + ir::Value* init_; + + private: + cell_t addr_ = kInvalidAddr; + bool read_ : 1; + bool written_ : 1; +}; + +class Argument final : public Variable { + public: + explicit Argument(ArgDecl* var, ir::Value* init) + : Variable(var, init) + {} + + VarDeclBase* arg_decl() const { return pn_u.arg_decl; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Argument; } +}; + +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_; } + + InsnBlock* body() const { return body_; } + void set_body(InsnBlock* 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; } + + const PoolArray& argv() const { return argv_; } + void set_argv(PoolArray&& argv) { argv_ = std::move(argv); } + + uint32_t num_slots() { return num_slots_; } + void set_num_slots(uint32_t slots) { num_slots_ = slots; } + + static constexpr uint32_t kInvalidSlot = std::numeric_limits::max(); + + private: + InsnBlock* 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; + uint32_t num_slots_ = 0; + bool is_live_ = false; + PoolArray argv_; +}; + +class Return final : public Insn { + public: + Return(ReturnStmt* stmt, Value* val) + : Insn(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 Insn { + public: + Exit(ExitStmt* stmt, Value* val) + : Insn(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 Insn { + public: + Assert(AssertStmt* stmt, Value* val) + : Insn(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 Insn { + public: + ValueInsn(ExprStmt* stmt, Value* val) + : Insn(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 Insn { + public: + explicit Break(BreakStmt* stmt) + : Insn(IrKind::Break, stmt) + {} + + static bool is_a(Node* node) { return node->kind() == IrKind::Break; } +}; + +class Continue final : public Insn { + public: + explicit Continue(ContinueStmt* stmt) + : Insn(IrKind::Continue, stmt) + {} + + static bool is_a(Node* node) { return node->kind() == IrKind::Continue; } +}; + +class If final : public Insn { + public: + If(IfStmt* stmt, Value* cond, InsnBlock* on_true, InsnBlock* on_false) + : Insn(IrKind::If, stmt), + cond_(cond), + on_true_(on_true), + on_false_(on_false) + {} + + Value* cond() const { return cond_; } + InsnBlock* on_true() const { return on_true_; } + InsnBlock* on_false() const { return on_false_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::If; } + + private: + Value* cond_; + InsnBlock* on_true_; + InsnBlock* on_false_; +}; + +class DoWhile final : public Insn { + public: + DoWhile(DoWhileStmt* stmt, Value* cond, InsnBlock* body) + : Insn(IrKind::DoWhile, stmt), + cond_(cond), + body_(body) + {} + + DoWhileStmt* stmt() const { return pn_u.do_while_stmt; } + Value* cond() const { return cond_; } + InsnBlock* body() const { return body_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::DoWhile; } + + private: + Value* cond_; + InsnBlock* body_; +}; + +class Delete final : public Insn { + public: + Delete(DeleteStmt* stmt, Value* val, Function* dtor) + : Insn(IrKind::Delete, stmt), + val_(val), + dtor_(dtor) + {} + + Value* val() const { return val_; } + Function* dtor() const { return dtor_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Delete; } + + private: + Value* val_; + Function* dtor_; +}; + +class ForLoop final : public Insn { + public: + ForLoop(ForStmt* stmt, InsnBlock* init, Value* cond, InsnBlock* advance, InsnBlock* body) + : Insn(IrKind::ForLoop, stmt), + init_(init), + cond_(cond), + advance_(advance), + body_(body) + {} + + ForStmt* stmt() const { return pn_u.for_stmt; } + InsnBlock* init() const { return init_; } + Value* cond() const { return cond_; } + InsnBlock* advance() const { return advance_; } + InsnBlock* body() const { return body_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::ForLoop; } + + private: + InsnBlock* init_; + Value* cond_; + InsnBlock* advance_; + InsnBlock* body_; +}; + +class Switch final : public Insn { + public: + typedef std::pair, InsnBlock*> Case; + + Switch(SwitchStmt* stmt, Value* cond, std::vector&& cases, InsnBlock* default_case) + : Insn(IrKind::Switch, stmt), + cond_(cond), + cases_(std::move(cases)), + default_case_(default_case) + {} + + Value* cond() const { return cond_; } + const PoolArray& cases() const { return cases_; } + InsnBlock* default_case() const { return default_case_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Switch; } + + private: + Value* cond_; + PoolArray cases_; + InsnBlock* 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, Function* target, Value* first = nullptr, + Value* second = nullptr, bool swapped = false) + : Value(IrKind::CallUserOp, expr, type), + target_(target), + first_(first), + second_(second), + swapped_(swapped) + {} + + Function* target() const { return target_; } + Value* first() const { return first_; } + Value* second() const { return second_; } + bool swapped() const { return swapped_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::CallUserOp; } + + private: + Function* 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 : public Value { + public: + FunctionRef(Expr* expr, QualType type, Function* fun) + : FunctionRef(IrKind::FunctionRef, expr, type, fun) + {} + + Function* fun() const { return fun_; } + + static bool is_a(Node* op) { + return op->kind() == IrKind::FunctionRef || + op->kind() == IrKind::BoundFunction; + } + + protected: + FunctionRef(IrKind kind, Expr* expr, QualType type, Function* fun) + : Value(kind, expr, type), + fun_(fun) + {} + + private: + Function* fun_; +}; + +class BoundFunction final : public FunctionRef { + public: + BoundFunction(Expr* expr, QualType type, ir::Value* val, ir::Function* fun) + : FunctionRef(IrKind::BoundFunction, expr, type, fun), + val_(val) + {} + + ir::Value* val() const { return val_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::BoundFunction; } + + private: + ir::Value* val_; +}; + +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::FunctionRef* target, const std::vector& values) + : Value(IrKind::CallOp, expr, type), + target_(target) + { + new (&values_) decltype(values_)(values); + } + + ir::FunctionRef* target() const { return target_; } + const PoolArray& argv() const { return values_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::CallOp; } + + private: + ir::FunctionRef* 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 ArrayInitializer final : public Value { + public: + ArrayInitializer(ArrayExpr* expr, QualType type, std::vector&& values) + : Value(IrKind::ArrayInitializer, expr, type), + values_(std::move(values)) + {} + + ArrayExpr* expr() const { return pn_u.array_expr; } + const PoolArray& values() const { return values_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::ArrayInitializer; } + + private: + PoolArray values_; +}; + +class CastOp final : public Value { + public: + CastOp(Expr* expr, QualType type, ir::Value* val) + : Value(IrKind::CastOp, expr, type), + val_(val) + {} + + ir::Value* val() const { return val_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::CastOp; } + + private: + ir::Value* val_; +}; + +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::PropertyRef || + op->kind() == IrKind::FieldRef || + op->kind() == IrKind::StackRef; + } + + // Returns true if address calculation requires evaluating more than one + // operation and thus might be considered expensive. + bool HasComplexAddressCalculation(); + + // Returns true if this l-value is addressable, and false otherwise. + bool IsAddressable() { return kind() != IrKind::PropertyRef; } +}; + +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 : public Value { + public: + Store(Expr* expr, QualType type, Lvalue* lval, Value* val) + : Store(IrKind::Store, expr, type, lval, val) + {} + + Lvalue* lval() const { return lval_; } + Value* val() const { return val_; } + + // Not a StoreWithTemp, since it's a different op. We just use the base + // class for convenience. + static bool is_a(Node* op) { return op->kind() == IrKind::Store; } + + protected: + Store(IrKind kind, Expr* expr, QualType type, Lvalue* lval, Value* val) + : Value(kind, expr, type), + lval_(lval), + val_(val) + {} + + private: + Lvalue* lval_; + Value* val_; +}; + +class StoreWithTemp final : public Store { + public: + StoreWithTemp(Expr* expr, QualType type, Lvalue* lval, Value* val, uint32_t temp_slot) + : Store(IrKind::StoreWithTemp, expr, type, lval, val), + temp_slot_(temp_slot) + {} + + uint32_t temp_slot() const { return temp_slot_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::StoreWithTemp; } + + private: + uint32_t temp_slot_; +}; + +// Reference a stack slot that can hold one value. If |val| is non-null, then +// the stack ref is initialized with this value. +class StackRef final : public Lvalue { + public: + explicit StackRef(Expr* expr, QualType type, uint32_t slot) + : Lvalue(IrKind::StackRef, expr, type), + slot_(slot) + {} + + uint32_t slot() const { return slot_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::StackRef; } + + private: + uint32_t slot_; +}; + +class IncDec : public Value { + public: + IncDec(IncDecExpr* expr, QualType type, Lvalue* lval, bool used) + : IncDec(IrKind::IncDec, expr, type, lval, used) + {} + + Lvalue* lval() const { return lval_; } + bool used() const { return used_; } + IncDecExpr* expr() const { return pn_u.incdec_expr; } + + static bool is_a(Node* op) { + return op->kind() == IrKind::IncDec || + op->kind() == IrKind::IncDecWithTemp; + } + + protected: + IncDec(IrKind kind, IncDecExpr* expr, QualType type, Lvalue* lval, bool used) + : Value(kind, expr, type), + lval_(lval), + used_(used) + {} + + private: + Lvalue* lval_; + bool used_; +}; + +// Temporary slot for passing the loaded value. +class IncDecWithTemp final : public IncDec { + public: + IncDecWithTemp(IncDecExpr* expr, QualType type, Lvalue* lval, Value* val, uint32_t temp_slot, + bool used) + : IncDec(IrKind::IncDecWithTemp, expr, type, lval, used), + val_(val), + slot_(temp_slot) + {} + + Value* val() const { return val_; } + uint32_t slot() const { return slot_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::IncDecWithTemp; } + + private: + Value* val_; + uint32_t slot_; +}; + +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 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_; } + ir::Function* getter() const { return getter_; } + ir::Function* setter() const { return setter_; } + + void BindGetter(ir::Function* getter); + void BindSetter(ir::Function* setter); + + static bool is_a(Node* op) { return op->kind() == IrKind::PropertyRef; } + + private: + Value* val_; + MethodmapPropertyDecl* decl_; + + // This is a huge hack. Maybe we need a dictionary of PropertyDecls + // to ir::Functions. + ir::Function* getter_ = nullptr; + ir::Function* setter_ = nullptr; +}; + +// This is effectively a macro operation for: +// CommaOp( +// Store(StackRef(temp_slot), value), +// AddressOf(StackRef(temp_slot))) +class TempValueRef final : public Value { + public: + TempValueRef(Expr* expr, QualType type, Value* val, uint32_t slot) + : Value(IrKind::TempValueRef, expr, type), + val_(val), + slot_(slot) + {} + + ir::Value* val() const { return val_; } + uint32_t slot() const { return slot_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::TempValueRef; } + + private: + ir::Value* val_; + uint32_t slot_; +}; + +// Calculate the address of an l-value. Note that this is different from +// EmitLoadStorePrologue, which may return a reference (eg, the address of +// an IndexOp that points to another array). AddressOf computes an address +// of the effective value. +class AddressOf final : public Value { + public: + AddressOf(Expr* expr, QualType type, Lvalue* val) + : Value(IrKind::AddressOf, expr, type), + lval_(val) + {} + + Lvalue* lval() const { return lval_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::AddressOf; } + + private: + Lvalue* lval_; +}; + +} // namespace ir +} // namespace cc +} // namespace sp diff --git a/compiler/lexer.cpp b/compiler/lexer.cpp index 45bcddd73..94bdc6172 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) */ } @@ -330,8 +330,11 @@ void Lexer::HandleDirectives() { ifstack_.emplace_back(0); skiplevel_ = ifstack_.size(); + auto loc = pos(); cell val = 0; - preproc_expr(&val, NULL); /* get value (or 0 on error) */ + QualType ignore_type; + if (!preproc_expr(&val, &ignore_type)) + report(loc, 8); 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..5ba87875d 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,8 @@ 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*/ "expected an l-value\n", + }; diff --git a/compiler/name-resolution.cpp b/compiler/name-resolution.cpp index 2e3adf638..c4c4a5bd6 100644 --- a/compiler/name-resolution.cpp +++ b/compiler/name-resolution.cpp @@ -201,10 +201,10 @@ bool EnumDecl::EnterNames(SemaContext& sc) { for (const auto& field : fields_ ) { 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); + if (field->value() && field->value()->Bind(sc) && sc.sema()->CheckRvalue(field->value())) { + 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()->CheckRvalue(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; } @@ -401,40 +402,21 @@ bool VarDeclBase::Bind(SemaContext& sc) { if (!sc.BindType(pos(), &type_)) return false; + bool ok = true; if (!type_.dim_exprs.empty()) { - if (!ResolveArrayType(sc.sema(), this)) - return false; - } - - if (type()->isVoid()) - error(pos_, 144); - - bool def_ok = CheckNameRedefinition(sc, name_, pos_, vclass_); - - if (type_.type->isArray() && (!type_.has_postdims || implicit_dynamic_array())) { - if (vclass_ == sGLOBAL) - error(pos_, 162); - else if (vclass_ == sSTATIC) - error(pos_, 165); - } - - if (type()->isPstruct()) { - type_.is_const = true; - } else { - if (type_.is_varargs) - markusage(this, uREAD); + for (const auto& dim_expr : type_.dim_exprs) { + if (dim_expr) + ok &= dim_expr->Bind(sc); + } } - if (is_public_) - is_read_ = true; - - if (def_ok) + if (CheckNameRedefinition(sc, name_, pos_, vclass_)) DefineSymbol(sc, this, vclass_); // LHS bind should now succeed. if (init_) init_->left()->BindLval(sc); - return true; + return ok; } bool VarDeclBase::BindType(SemaContext& sc) { @@ -808,17 +790,13 @@ FunctionDecl::BindArgs(SemaContext& sc) { AutoCountErrors errors; - size_t arg_index = 0; for (auto& var : args_) { const auto& typeinfo = var->type_info(); AutoErrorPos pos(var->pos()); - if (typeinfo.is_varargs) { - /* redimension the argument list, add the entry iVARARGS */ - var->BindAddress(static_cast((arg_index + 3) * sizeof(cell))); + if (typeinfo.is_varargs) break; - } Type* type = typeinfo.type; if (type->isEnumStruct()) { @@ -826,19 +804,12 @@ FunctionDecl::BindArgs(SemaContext& sc) report(var->pos(), 135) << type; } - /* Stack layout: - * base + 0*sizeof(cell) == previous "base" - * base + 1*sizeof(cell) == function return address - * base + 2*sizeof(cell) == number of arguments - * base + 3*sizeof(cell) == first argument of the function - * So the offset of each argument is "(argcnt+3) * sizeof(cell)". - * - * Since arglist has an empty terminator at the end, we actually add 2. - */ - var->BindAddress(static_cast((arg_index + 3) * sizeof(cell))); - arg_index++; - - if (type->isArray() || typeinfo.type->isEnumStruct()) { + if (var->init()) + var->init()->Bind(sc); + +#if 0 + if (type->isArray() || type->isEnumStruct()) { + assert(false); if (sc.sema()->CheckVarDecl(var) && var->init_rhs()) fill_arg_defvalue(sc.cc(), var); } else { @@ -850,25 +821,26 @@ 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); } } - if (var->type()->isReference()) + if (type->isReference()) var->set_is_read(); if (is_callback_ || is_public_) var->set_is_read(); +#endif /* arguments of a public function may not have a default value */ if (is_public_ && var->default_value()) @@ -896,6 +868,8 @@ FunctionDecl::BindArgs(SemaContext& sc) canonical()->checked_one_signature = true; return errors.ok(); } + + // :TODO: check that we don't have two defargs. if (!canonical()->compared_prototype_args) { auto impl_fun = impl(); auto proto_fun = prototype(); 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..37b63391d 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 @@ -959,8 +947,6 @@ class CallExpr final : public Expr PoolArray& args() { return args_; } Expr* target() const { return target_; } int token() const { return token_; } - Expr* implicit_this() const { return implicit_this_; } - void set_implicit_this(Expr* expr) { implicit_this_ = expr; } FunctionDecl* fun() const { return fun_; } void set_fun(FunctionDecl* fun) { fun_ = fun; } @@ -971,7 +957,6 @@ class CallExpr final : public Expr Expr* target_; PoolArray args_; FunctionDecl* fun_ = nullptr; - Expr* implicit_this_ = nullptr; }; class EmitOnlyExpr : public Expr @@ -1401,7 +1386,7 @@ class DeleteStmt : public Stmt bool Bind(SemaContext& sc) override { return expr_->Bind(sc); } - void ProcessUses(SemaContext& sc) override; + void ProcessUses(SemaContext& sc) override {} static bool is_a(Stmt* node) { return node->kind() == StmtKind::DeleteStmt; } @@ -1469,7 +1454,7 @@ class DoWhileStmt : public Stmt class ForStmt : public Stmt { public: - explicit ForStmt(const token_pos_t& pos, Stmt* init, Expr* cond, Expr* advance, Stmt* body) + explicit ForStmt(const token_pos_t& pos, Stmt* init, Expr* cond, ExprStmt* advance, Stmt* body) : Stmt(StmtKind::ForStmt, pos), scope_(nullptr), init_(init), @@ -1487,7 +1472,7 @@ class ForStmt : public Stmt Stmt* init() const { return init_; } Expr* cond() const { return cond_; } Expr* set_cond(Expr* cond) { return cond_ = cond; } - Expr* advance() const { return advance_; } + ExprStmt* advance() const { return advance_; } Stmt* body() const { return body_; } bool always_taken() const { return always_taken_; } void set_always_taken(bool val) { always_taken_ = val; } @@ -1500,7 +1485,7 @@ class ForStmt : public Stmt SymbolScope* scope_; Stmt* init_; Expr* cond_; - Expr* advance_; + ExprStmt* advance_; Stmt* body_; bool always_taken_ = false; bool never_taken_ = false; diff --git a/compiler/parser.cpp b/compiler/parser.cpp index c6f004351..35a141dbf 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) @@ -313,9 +314,20 @@ bool Parser::PreprocExpr(cell* val, Type** type) { sema.set_context(&sc); - if (!expr->Bind(sc) || !sema.CheckExpr(expr)) + if (!expr->Bind(sc)) return false; - return expr->EvalConst(val, type); + + // :TODO: merge this with EvalConst + ir::Value* irv = sema.CheckRvalue(expr); + if (!irv) + return false; + if (auto cv = irv->as()) { + *val = cv->value(); + *type = cv->type(); + return true; + } + + return Expr::EvalConst(expr, val, type); } Stmt* @@ -1587,9 +1599,10 @@ Parser::parse_for() lexer_->need(';'); } - Expr* advance = nullptr; + ExprStmt* advance = nullptr; if (!lexer_->match(endtok)) { - advance = parse_expr(false); + if (auto expr = parse_expr(false)) + advance = new ExprStmt(expr->pos(), expr); lexer_->need(endtok); } 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/sc.h b/compiler/sc.h index 9903528e6..384e4660f 100644 --- a/compiler/sc.h +++ b/compiler/sc.h @@ -25,8 +25,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 @@ -72,6 +70,7 @@ struct DefaultArg : public PoolObject { // Values for symbol::usage. #define uREAD 0x1 // Used/accessed. #define uWRITTEN 0x2 // Altered/written (variables only). +#define uMAYBE_WRITTEN 0x4 #define uMAINFUNC "main" diff --git a/compiler/semantics.cpp b/compiler/semantics.cpp index c9a83e1eb..9686b3946 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,15 +40,15 @@ 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); - ke::SaveAndSet push_sc(&sc_, &sc); + global_sc_.emplace(this); + ke::SaveAndSet push_sc(&sc_, &global_sc_.value()); AutoCountErrors errors; if (!CheckStmtList(tree->stmts()) || !errors.ok()) @@ -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; @@ -92,9 +101,14 @@ bool Semantics::CheckStmt(Stmt* stmt, StmtFlags flags) { if (flags & STMT_OWNS_HEAP) restore_heap_ownership.init(&pending_heap_allocation_, false); - auto owns_heap = ke::MakeScopeGuard([&, this]() { + std::vector temp_slots; + ke::SaveAndSet*> push_temp_slots(&temp_slots_, &temp_slots); + + auto cleanup = ke::MakeScopeGuard([&, this]() { if (flags & STMT_OWNS_HEAP) AssignHeapOwnership(stmt); + while (!temp_slots.empty()) + sc_->FreeTempSlot(ke::PopBack(&temp_slots)); }); switch (stmt->kind()) { @@ -158,53 +172,95 @@ bool Semantics::CheckStmt(Stmt* stmt, StmtFlags flags) { } bool Semantics::CheckVarDecl(VarDeclBase* decl) { - AutoErrorPos aep(decl->pos()); - - const auto& type = decl->type(); - bool is_const = decl->type_info().is_const; + ir::Value* init; + if (!CheckVarDeclCommon(decl, &init)) + return false; + // :TODO: remove ident // Constants are checked during binding. if (decl->ident() == iCONSTEXPR) return true; + 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); + } + return true; +} + +bool Semantics::CheckVarDeclCommon(VarDeclBase* decl, ir::Value** out_init) { + AutoErrorPos aep(decl->pos()); + + *out_init = nullptr; + + auto& ti = *decl->mutable_type_info(); + if (!ti.dim_exprs.empty()) { + if (!ResolveArrayType(this, decl)) + return false; + } + + auto type = decl->type(); + if (type->isVoid()) + report(decl, 144); + + if (type->isArray() && (!ti.has_postdims || decl->implicit_dynamic_array())) { + if (decl->vclass() == sGLOBAL) + report(decl, 162); + else if (decl->vclass() == sSTATIC) + report(decl, 165); + } + + // :TODO: check arrays dont have pstructs + // :TODO: check no arg if pstruct + // :TODO: supply init if (type->isPstruct()) return CheckPstructDecl(decl); - if (!decl->as() && is_const && !decl->init() && !decl->is_public()) - report(decl->pos(), 251); + if (decl->as()) { + if (ti.is_varargs) + markusage(decl, uREAD); + } else { + if (decl->is_public()) + decl->set_is_read(); + + if (ti.is_const && !decl->init() && !decl->is_public() && decl->ident() != iCONSTEXPR) + report(decl->pos(), 251); + } - // CheckArrayDecl works on enum structs too. if (type->isArray() || type->isEnumStruct()) { - if (!CheckArrayDeclaration(decl)) + if (!CheckArrayDeclaration(decl, out_init)) return false; if (decl->vclass() == sLOCAL) pending_heap_allocation_ = true; - 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; + } else if (auto rhs = decl->init_rhs()) { + ir::Value* init; + if ((init = CheckRvalue(rhs)) == nullptr) + return false; - auto vclass = decl->vclass(); - auto init_rhs = decl->init_rhs(); - if (init && vclass != sLOCAL) { - if (!init_rhs->EvalConst(nullptr, nullptr)) { - if (vclass == sARGUMENT && init_rhs->is(ExprKind::SymbolExpr)) - return true; - report(init_rhs->pos(), 8); + if (decl->vclass() != sLOCAL) { + if (!init->as()) { + assert(false); +#if 0 + if (decl->vclass() == sARGUMENT && init_rhs->is(ExprKind::SymbolExpr)) + return true; +#endif + report(init->pn()->pos(), 8); + } } + *out_init = init; } - return true; } bool Semantics::CheckPstructDecl(VarDeclBase* decl) { + decl->mutable_type_info()->is_const = true; + if (!decl->init()) return true; @@ -328,13 +384,13 @@ static inline int GetOperToken(int token) { } } -bool Semantics::CheckExpr(Expr* expr) { +ir::Value* Semantics::CheckExpr(Expr* expr, ExprFlags flags) { AutoErrorPos aep(expr->pos()); switch (expr->kind()) { case ExprKind::UnaryExpr: return CheckUnaryExpr(expr->to()); case ExprKind::IncDecExpr: - return CheckIncDecExpr(expr->to()); + return CheckIncDecExpr(expr->to(), flags); case ExprKind::BinaryExpr: return CheckBinaryExpr(expr->to()); case ExprKind::LogicalExpr: @@ -344,11 +400,11 @@ bool Semantics::CheckExpr(Expr* expr) { case ExprKind::TernaryExpr: return CheckTernaryExpr(expr->to()); case ExprKind::CastExpr: - return CheckCastExpr(expr->to()); + return CheckCastExpr(expr->to(), flags); case ExprKind::SymbolExpr: - return CheckSymbolExpr(expr->to(), false); + return CheckSymbolExpr(expr->to(), flags); case ExprKind::CommaExpr: - return CheckCommaExpr(expr->to()); + return CheckCommaExpr(expr->to(), flags); case ExprKind::ThisExpr: return CheckThisExpr(expr->to()); case ExprKind::NullExpr: @@ -360,7 +416,7 @@ bool Semantics::CheckExpr(Expr* expr) { case ExprKind::IndexExpr: return CheckIndexExpr(expr->to()); case ExprKind::FieldAccessExpr: - return CheckFieldAccessExpr(expr->to(), false); + return CheckFieldAccessExpr(expr->to(), flags); case ExprKind::CallExpr: return CheckCallExpr(expr->to()); case ExprKind::NewArrayExpr: @@ -369,26 +425,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(), flags); 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 +442,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 +524,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 +579,99 @@ 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)) + return op; + + if (auto cv = val->as()) + return new ir::Const(unary, type, !cv->value()); + + return new ir::UnaryOp(unary, types_->get_bool(), val); + } + case '-': { + // Since array initializers need constexprs, and we don't lex '-' as + // part of a number (yet), constant fold floats as a convenience hack. + auto cv = val->as(); + if (cv && cv->type()->isFloat()) { + float f = sp::FloatCellUnion(cv->value()).f32; + cell_t new_value = sp::FloatCellUnion(-f).cell; + return new ir::Const(unary, val->type(), new_value); } - break; + + if (auto op = MaybeCallUserOp(unary, '-', val, nullptr)) + return op; + + if (cv) + return new ir::Const(unary, val->type(), -cv->value()); + + 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, ExprFlags flags) { AutoErrorPos aep(incdec->pos()); - auto expr = incdec->expr(); - if (!CheckExpr(expr)) - return false; - if (!CheckScalarType(expr)) - return false; - if (!expr->lvalue()) { - report(incdec, 22); - return false; - } + auto val = CheckExpr(incdec->expr(), ExprFlags::DEFAULT); + if (!val) + return nullptr; + auto lval = BindLvalue(val, uREAD | uWRITTEN); + if (!lval) + 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 (!expr_val.accessor()->getter()) { - report(incdec, 149) << expr_val.accessor()->name(); - return false; - } - markusage(expr_val.accessor()->getter(), uREAD); - markusage(expr_val.accessor()->setter(), uREAD); - } + auto type = BuildRvalueType(lval->type()); + if (!CheckScalarType(incdec, type)) + return nullptr; - Type* type = expr_val.type(); - if (type->isReference()) - type = type->inner(); + bool used = !(flags & ExprFlags::RESULT_UNUSED); - find_userop(*sc_, incdec->token(), type, 0, 1, &expr_val, &incdec->userop()); + if (UserOp userop = FindUserOp(incdec, incdec->token(), type, QualType()); userop.target) { + uint32_t slot = AllocTempSlot(); + auto ref = new ir::StackRef(incdec, type, slot); + auto userop_ir = new ir::CallUserOp(incdec, + QualType(userop.target->decl()->return_type()), + userop.target, ref); + return new ir::IncDecWithTemp(incdec, type, lval, userop_ir, slot, used); + } - // :TODO: more type checks - auto& val = incdec->val(); - val.ident = iEXPRESSION; - val.set_type(type); - return true; + return new ir::IncDec(incdec, type, lval, used); } void @@ -705,180 +697,164 @@ 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; - } + int oper_token = GetOperToken(token); + + auto left = CheckExpr(expr->left(), ExprFlags::DEFAULT); + if (!left) + return nullptr; + auto right = CheckRvalue(expr->right()); + if (!right) + return nullptr; + auto temp_slot = ir::Function::kInvalidSlot; + ir::Lvalue* lval = nullptr; if (IsAssignOp(token)) { - // Mark the left-hand side as written as soon as we can. - if (Decl* sym = left->val().sym) { - markusage(sym, uWRITTEN); + uint8_t flags = uWRITTEN; + if (token != '=') + flags |= uREAD; + if ((lval = BindLvalue(left, flags)) == nullptr) + return nullptr; + + if (!CheckAssignmentLHS(expr, lval)) + return nullptr; - // If it's an outparam, also mark it as read. - if (sym->vclass() == sARGUMENT && - (sym->type()->isReference() || sym->type()->isArray())) + if (token != '=') { + if (!lval->IsAddressable() || lval->HasComplexAddressCalculation() || + lval->HasSideEffects()) { - markusage(sym, uREAD); - } - } else if (auto* accessor = left->val().accessor()) { - if (!accessor->setter()) { - report(expr, 152) << accessor->name(); - return false; + temp_slot = AllocTempSlot(); + left = new ir::Load(expr->left(), lval->type(), + new ir::StackRef(expr->left(), lval->type(), temp_slot)); + } else { + // Using the same lval twice is kind of hacky, but should be + // safe as address calculation is constant with respect to the + // rest of the statement. + left = BuildRvalue(expr->left(), lval); } - 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)); + } else { + if ((left = BindRvalue(expr->left(), left)) == nullptr) + return nullptr; } - // 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(); + ir::Value* out = nullptr; - auto oper_tok = expr->oper(); - if (oper_tok) { - assert(token != '='); - - 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; - } - 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; - } - /* ??? ^^^ should do same kind of error checking with functions */ - } + if (token != '=') { + if (!CheckScalarType(expr->left(), left->type())) + return nullptr; + if (!CheckScalarType(expr->right(), right->type())) + return nullptr; - // The assignment operator is overloaded separately. - if (IsAssignOp(token)) { - if (!CheckAssignmentRHS(expr)) - return false; + out = MaybeCallUserOp(expr, oper_token, left, right); } - auto& val = expr->val(); - val.ident = iEXPRESSION; - val.set_type(left_val.type()); - - auto& assignop = expr->assignop(); - if (assignop.sym) - val.set_type(assignop.sym->type()); - - 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(); + if (!out) + TypeChecker::DoCoerce(expr->pos(), *left->type(), *right->type(), TypeChecker::Commutative); - Type* right_type = right_val.type(); - if (right_type->isReference()) - right_type = right_type->inner(); + if (token != '=') { + 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(), oper_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); + } - TypeChecker::DoCoerce(expr->pos(), left_type, right_type, TypeChecker::Commutative); + if (!out) { + QualType type; + if (IsChainedOp(oper_token) || oper_token == tlEQ || oper_token == tlNE) + type = types_->get_bool(); + else + type = left->type(); + out = new ir::BinaryOp(expr, type, oper_token, left, right); } + } else { + out = right; + } - if (IsChainedOp(token) || token == tlEQ || token == tlNE) - val.set_type(types_->type_bool()); + if (IsAssignOp(token)) { + auto type = BuildRvalueType(lval->type()); + if (temp_slot == ir::Function::kInvalidSlot) + out = new ir::Store(expr, type, lval, out); + else + out = new ir::StoreWithTemp(expr, type, lval, out, temp_slot); } - return true; + 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; } + if (auto prop = lval->as()) { + if (!prop->decl()->setter()) { + report(expr, 152) << prop->decl()->name(); + 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 (auto left_array = left_val.type()->as()) { - TypeChecker tc(expr, left_val.type(), right_val.type(), TypeChecker::Assignment); + 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 = 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 +867,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 +896,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 +908,51 @@ 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()) { + if (value) + *value = tve->value(); + if (type) + *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 +965,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 +999,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; +ir::Value* Semantics::CheckChainedCompareExpr(ChainedCompareExpr* chain) { + auto first = CheckRvalue(chain->first()); + if (!first) + return nullptr; - auto& val = chain->val(); - val.ident = iEXPRESSION; - val.set_type(types_->type_bool()); + 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; - for (auto& op : chain->ops()) { - Expr* right = op.expr; - const auto& left_val = left->val(); - const auto& right_val = right->val(); + assert(!right->HasSideEffects()); - 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; - } - - 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 +1067,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; + 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.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; - } - - val_.set_constval(cond ? left : right); - return true; + return new ir::TernaryOp(expr, type, first, second, third); } void @@ -1206,26 +1115,26 @@ TernaryExpr::ProcessDiscardUses(SemaContext& sc) third_->ProcessUses(sc); } -bool Semantics::CheckCastExpr(CastExpr* expr) { +ir::Value* Semantics::CheckCastExpr(CastExpr* expr, ExprFlags flags) { 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()); + ir::Value* val = CheckExpr(expr->expr(), flags); + if (!val) + return nullptr; - Type* ltype = out_val.type(); + if (auto lval = val->as()) { + if ((flags & ExprFlags::WANT_RVALUE) == ExprFlags::WANT_RVALUE) + val = BindRvalue(expr->expr(), lval); + } - 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 +1143,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 new ir::CastOp(expr, to_type, val); } void @@ -1276,48 +1192,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, ExprFlags flags) { 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 +1236,62 @@ 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 = decl->as(); + assert(var_decl); + + ir::Variable* var = nullptr; + if (var_decl->vclass() == sGLOBAL || var_decl->vclass() == sSTATIC) { + auto it = global_vars_.find(var_decl); + assert(it != global_vars_.end()); + var = it->second; + } else { + auto it = sc_->local_vars().find(var_decl); + assert(it != sc_->local_vars().end()); + var = it->second; + } + + return new ir::VariableRef(expr, QualType(type), var); + } + case iFUNCTN: { + auto fun = BuildFunction(decl->to()); + return BuildFunctionRef(expr, fun); + } case iTYPENAME: - if (!allow_types) { + if (!(flags & ExprFlags::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, ExprFlags flags) { 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], flags); - 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 = CheckRvalue(expr, ExprFlags::RESULT_UNUSED); + if (!val) + return nullptr; + values.emplace_back(val); } for (size_t i = 0; i < comma->exprs().size() - 1; i++) { @@ -1367,14 +1300,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 +1318,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 +1394,104 @@ IndexExpr::ProcessUses(SemaContext& sc) expr_->MarkAndProcessUses(sc); } -bool Semantics::CheckThisExpr(ThisExpr* expr) { - auto sym = expr->decl(); - assert(sym->ident() == iVARIABLE); +ir::Value* Semantics::CheckThisExpr(ThisExpr* expr) { + auto decl = expr->decl(); + auto it = sc_->local_vars().find(decl); + assert(it != sc_->local_vars().end()); + auto var = it->second; + + var->set_read(); - auto& val = expr->val(); - val.ident = sym->ident(); - val.sym = sym; - val.set_type(sym->type()); - expr->set_lvalue(true); - return true; + // |this| is never an l-value. + auto ref = new ir::VariableRef(expr, QualType(decl->type()), var); + return BindRvalue(expr, ref); } -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, ExprFlags flags) { 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 = CheckRvalue(expr->base(), ExprFlags::ALLOW_TYPES); + if (!base) + return nullptr; int token = expr->token(); if (token == tDBLCOLON) - return CheckStaticFieldAccessExpr(expr); + return CheckStaticFieldAccessExpr(expr, base); - 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, flags); 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) { + if (!(flags & ExprFlags::ALLOW_BOUND_FUNCTIONS)) { report(expr, 50); - return false; + return nullptr; } - val.ident = iFUNCTN; - val.sym = method; - markusage(method, uREAD); - return true; + auto fun = BuildFunction(method); + return new ir::BoundFunction(expr, QualType(method->return_type()), base, fun); } void @@ -1638,46 +1500,37 @@ FieldAccessExpr::ProcessUses(SemaContext& sc) base_->MarkAndProcessUses(sc); } -FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { +ir::FunctionRef* Semantics::BindCallTarget(CallExpr* call, Expr* target) { AutoErrorPos aep(target->pos()); switch (target->kind()) { case ExprKind::FieldAccessExpr: { auto expr = target->to(); - if (!CheckFieldAccessExpr(expr, true)) + auto ir = CheckFieldAccessExpr(expr, ExprFlags::ALLOW_BOUND_FUNCTIONS); + if (!ir) return nullptr; - auto& val = expr->val(); - if (val.ident != iFUNCTN) { + auto ref = ir->as(); + if (!ref) { report(target, 12); return nullptr; } + auto fun = ref->fun(); + // The static accessor (::) is offsetof(), so it can't return functions. assert(expr->token() == '.'); - auto resolved = expr->resolved(); - if (auto method = resolved->as()) { + if (auto method = fun->decl()->as()) { auto map = method->parent()->as(); if (map->ctor() == method) { report(call, 84) << method->parent()->name(); return nullptr; } } - - auto method = resolved->as(); - assert(resolved->as() || method); - - auto base = expr->base(); - if (base->lvalue()) - base = expr->set_base(new RvalueExpr(base)); - if (resolved->as() || !method->is_static()) - call->set_implicit_this(base); - return val.sym->as()->canonical(); + return ref; } case ExprKind::SymbolExpr: { - call->set_implicit_this(nullptr); - auto expr = target->to(); auto decl = expr->decl(); if (auto mm = decl->as()) { @@ -1691,7 +1544,7 @@ FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { report(target, 170) << decl->name(); return nullptr; } - return mm->ctor(); + return BuildFunctionRef(expr, mm->ctor()); } auto fun = decl->as(); if (!fun) { @@ -1704,7 +1557,7 @@ FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { report(target, 4) << decl->name(); return nullptr; } - return fun; + return BuildFunctionRef(expr, fun); } default: report(target, 12); @@ -1712,7 +1565,7 @@ FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { } } -FunctionDecl* Semantics::BindNewTarget(Expr* target) { +ir::FunctionRef* Semantics::BindNewTarget(Expr* target) { AutoErrorPos aep(target->pos()); switch (target->kind()) { @@ -1734,155 +1587,118 @@ FunctionDecl* Semantics::BindNewTarget(Expr* target) { report(expr, 172) << mm->name(); return nullptr; } - return mm->ctor(); + return BuildFunctionRef(expr, 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, ExprFlags flags) { - 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) { + if (!(flags & ExprFlags::ALLOW_BOUND_FUNCTIONS)) { 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, ir::Value* base) { AutoErrorPos aep(expr->pos()); - auto base = expr->base(); - const auto& base_val = base->val(); - if (base_val.ident != iTYPENAME) { + auto type_ref = base->as(); + if (!type_ref) { report(expr, 108); - return false; + return nullptr; } - Type* type = base_val.type(); - Decl* field = FindEnumStructField(type, expr->name()); + Decl* field = FindEnumStructField(*base->type(), expr->name()); if (!field) { - report(expr, 105) << type << expr->name(); - return false; + report(expr, 105) << base->type() << expr->name(); + 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 = CheckExpr(child, ExprFlags::ALLOW_TYPES); + if (!val) + return nullptr; - auto& val = expr->val(); - val.set_type(types_->type_int()); + 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()); + } - 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; + return new ir::Const(child, types_->get_int(), 1); - case iARRAYCHAR: - report(expr, 252); - val.set_constval(1); - return true; + case TypeKind::EnumStruct: + return new ir::Const(child, types_->get_int(), type->asEnumStruct()->array_size()); - case iTYPENAME: { - auto es = cv.sym->as(); - if (!es) { - report(child, 72); - return false; - } - val.set_constval(es->array_size()); - return true; - } - - 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,69 +1725,59 @@ 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::FunctionRef* ref; if (call->token() == tNEW) - fun = BindNewTarget(call->target()); + ref = BindNewTarget(call->target()); else - fun = BindCallTarget(call, call->target()); - if (!fun) - return false; - - assert(fun->canonical() == fun); - - call->set_fun(fun); + ref = BindCallTarget(call, call->target()); + if (!ref) + return nullptr; - if (fun->return_type()->isArray() || fun->return_type()->isEnumStruct()) { + FunctionDecl* decl = ref->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(ref->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(); - if (call->implicit_this()) { - if (arglist.empty()) { - report(call->implicit_this(), 92); - return false; - } - Expr* param = CheckArgument(call, arglist[0], call->implicit_this(), &ps, 0); - if (!param) - return false; - ps.argv[0] = param; - nargs++; - argidx++; - } + unsigned int nargs = 0; + auto& arglist = decl->args(); + + unsigned int implicit_args = 0; + if (ref->as()) + implicit_args++; + + unsigned int argidx = implicit_args; bool namedparams = false; 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 +1785,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,51 +1818,44 @@ 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 // arguments. - for (unsigned int argidx = 0; argidx < arglist.size(); argidx++) { - auto arg = arglist[argidx]; + for (unsigned int iter = implicit_args; iter < arglist.size(); iter++) { + auto arg = arglist[iter]; if (arg->type_info().is_varargs) break; - if (argidx >= ps.argv.size() || !ps.argv[argidx]) { - auto result = CheckArgument(call, arg, nullptr, &ps, argidx); + //if (ref->as() && iter == 0) + // continue; + if (iter >= ps.argv.size() || !ps.argv[iter]) { + auto result = CheckArgument(call, arg, nullptr, &ps, iter); if (!result) - return false; - ps.argv[argidx] = result; + return nullptr; + ps.argv[iter] = result; } - Expr* expr = ps.argv[argidx]; +#if 0 + Expr* expr = ps.argv[iter]; 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()), ref, 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); - unsigned int visual_pos = call->implicit_this() ? pos : pos + 1; + unsigned int visual_pos = pos + 1; +#if 0 if (!param || param->as()) { if (arg->type_info().is_varargs) { report(call, 92); // argument count mismatch @@ -2082,78 +1881,94 @@ Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, // The rest of the code to handle default values is in DoEmit. return param; } +#endif - if (param != call->implicit_this()) { - if (!CheckRvalue(param)) - return nullptr; - } + ir::Value* val = CheckExpr(param, ExprFlags::DEFAULT); + if (!val) + return nullptr; AutoErrorPos aep(param->pos()); - 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); + // Varargs are always passed by reference. + if (auto lval = val->as()) { + if (!BindLvalue(lval, uREAD)) + return nullptr; + + // If the value is already a reference we can load it. Or, if it's + // not addressable, we also need to load it. + // + // If it's not a reference and it's addressable, we can use the + // specialized AddressOf operation. + if (lval->type()->isReferenceType()) { + val = BuildRvalue(param, lval); + } else if (!lval->IsAddressable()) { + val = new ir::TempValueRef(param, val->type(), BuildRvalue(param, lval), + AllocTempSlot()); + } else { + val = new ir::AddressOf(param, lval->type(), lval); } - } else if (val->ident == iCONSTEXPR || val->ident == iEXPRESSION) { - NeedsHeapAlloc(param); + } else if (!val->type()->isReferenceType()) { + val = new ir::TempValueRef(param, val->type(), val, AllocTempSlot()); } - if (!checktag_string(arg->type(), val) && !checktag(arg->type(), val->type())) + + if (!checktag_string(arg->type(), val->type().ptr()) && !checktag(arg->type(), val->type().ptr())) report(param, 213) << arg->type() << val->type(); } else if (arg->type()->isReference()) { - assert(!handling_this); - - if (!lvalue || val->ident == iARRAYCHAR) { - report(param, 35) << visual_pos; // argument type mismatch + auto lval = val->as(); + if (!lval) { + report(param, 35) << visual_pos; return nullptr; } - if (val->sym && val->sym->is_const() && !arg->type_info().is_const) { - report(param, 35) << visual_pos; // argument type mismatch + if (!BindLvalue(lval, uREAD)) + return nullptr; + if (lval->as() && lval->type()->isChar()) { + report(param, 35) << visual_pos; return nullptr; } - checktag(arg->type()->inner(), val->type()); + if (lval->type().is_const() && !arg->type_info().is_const) { + report(param, 35) << visual_pos; + return nullptr; + } + + checktag(arg->type()->inner(), *val->type()); + + val = new ir::AddressOf(param, lval->type(), lval); } else if (arg->type()->isArray()) { // If the input type is an index into an array, create an implicit // array type to represent the slice. + assert(!val->as()); + +#if 0 Type* type = val->type(); if (val->ident == iARRAYCELL || val->ident == iARRAYCHAR) type = types_->defineArray(type, 0); +#endif + + if (auto lval = val->as()) + val = BindRvalue(param, lval); - TypeChecker tc(param, arg->type(), type, TypeChecker::Argument); + TypeChecker tc(param, QualType(arg->type()), val->type(), TypeChecker::Argument); if (!tc.Coerce()) return nullptr; - if (val->sym && val->sym->is_const() && !arg->type_info().is_const) { + if (val->type().is_const() && !arg->type_info().is_const) { report(param, 35) << visual_pos; // argument type mismatch return nullptr; } } else { - if (lvalue) { - param = new RvalueExpr(param); - val = ¶m->val(); - } + if (auto lval = val->as(); lval) + val = BindRvalue(param, lval); +#if 0 // Do not allow user operators to transform |this|. UserOperation userop; if (!handling_this && @@ -2166,8 +1981,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 +2001,48 @@ 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) { +ir::Value* Semantics::CheckNewArrayExprForArrayInitializer(NewArrayExpr* na) { + assert(false); + return nullptr; +#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 +2053,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,7 +2064,7 @@ bool Semantics::CheckNewArrayExprForArrayInitializer(NewArrayExpr* na) { assert(na->type()->isArray()); na->set_analysis_result(true); - return true; +#endif } void @@ -2259,22 +2075,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::InsnBlock* on_true = nullptr; + ir::InsnBlock* 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 +2120,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(), ExprFlags::RESULT_UNUSED); + if (!val) return false; - if (!expr->HasSideEffects()) - report(expr, 215); + + if (!val->HasSideEffects()) + report(stmt, 215); + + ir_->emplace(stmt, val); return true; } @@ -2397,7 +2228,7 @@ bool Semantics::CheckBlockStmt(BlockStmt* block) { // Blocks always taken heap ownership. AssignHeapOwnership(block); - return ok; + return true; } AutoCollectSemaFlow::AutoCollectSemaFlow(SemaContext& sc, ke::Maybe* out) @@ -2419,11 +2250,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 +2270,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 +2287,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 +2300,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 +2356,28 @@ 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(), ExprFlags::DEFAULT); + 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; - } + // Only grab an l-value if it can be set. + auto lval = BindLvalue(val, uREAD); + if (lval && !BindLvalue(lval, uMAYBE_WRITTEN)) + val = BuildRvalue(stmt->expr(), lval); + auto type = val->type(); auto map = type->asMethodmap(); if (!map) { - report(expr, 115) << "type" << v.type(); + report(stmt, 115) << "type" << type; return false; } @@ -2578,51 +2389,48 @@ 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); + auto dtor = BuildFunction(map->dtor()); + if (!dtor) + return false; - stmt->set_map(map); + ir_->emplace(stmt, val, 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::InsnBlock* body; bool has_break = false; bool has_return = false; ke::Maybe always_returns; @@ -2631,8 +2439,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 +2464,44 @@ 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::InsnBlock* 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::InsnBlock* advance = nullptr; if (stmt->advance()) { - ke::SaveAndSet restore(&pending_heap_allocation_, false); - if (CheckRvalue(stmt->advance())) - AssignHeapOwnership(stmt->advance()); + ir::NodeListBuilder builder(&ir_); + if (CheckStmt(stmt->advance(), STMT_OWNS_HEAP)) + advance = builder.Finish(); else ok = false; } ke::Maybe constval; - if (cond && cond->val().ident == iCONSTEXPR) - constval.init(cond->val().constval()); + if (cond) { + if (auto cv = cond->as()) + constval.init(cv->value()); + } + ir::InsnBlock* body_ir; bool has_break = false; bool has_return = false; ke::Maybe always_returns; @@ -2690,7 +2511,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 +2546,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 +2576,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::InsnBlock* 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 +2633,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 +2698,27 @@ bool Semantics::CheckFunctionDeclImpl(FunctionDecl* info) { report(info->pos(), 141); } + auto fun = BuildFunction(info); + ke::SaveAndSet push_fun(&fun_, fun); + + // :TODO: hack. + sc.set_fun(fun); + + bool ok = true; + for (const auto& arg : fun->argv()) { + assert(sc_->local_vars().find(arg->decl()) == sc_->local_vars().end()); + sc_->local_vars().emplace(arg->decl(), arg); + + ok &= CheckFunctionArgument(arg); + } + + 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 +2780,60 @@ 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(); + + ir::Function* fun = nullptr; + if (auto iter = functions_.find(canonical); iter != functions_.end()) { + fun = iter->second; + } else { + SemaContext arg_sc(*global_sc_, decl); + ke::SaveAndSet push_sc(&sc_, &arg_sc); + + // Build arguments. + PoolArray argv(canonical->args().size()); + for (size_t i = 0; i < canonical->args().size(); i++) { + const auto& arg = canonical->args().at(i); + + ir::Value* init; + if (!CheckVarDeclCommon(arg, &init)) + return nullptr; + + argv[i] = new ir::Argument(arg, init); + } + + fun = new ir::Function(canonical); + fun->set_argv(std::move(argv)); + + functions_.emplace(canonical, fun); + mod_->functions().emplace_back(fun); + } + + if (fun_) + fun_->AddReferenceTo(fun); + + return fun; +} + +ir::FunctionRef* Semantics::BuildFunctionRef(Expr* expr, ir::Function* 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); +} + +bool Semantics::CheckFunctionArgument(ir::Argument* arg) { + assert(!arg->arg_decl()->init()); + return true; } void Semantics::CheckFunctionReturnUsage(FunctionDecl* info) { @@ -3066,10 +2984,8 @@ void Semantics::NeedsHeapAlloc(Expr* expr) { } void Semantics::AssignHeapOwnership(ParseNode* node) { - if (pending_heap_allocation_) { - node->set_tree_has_heap_allocs(true); - pending_heap_allocation_ = false; - } + if (pending_heap_allocation_) + ir_->set_has_heap_allocs(); } void MethodmapDecl::ProcessUses(SemaContext& sc) { @@ -3153,12 +3069,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 +3097,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,25 +3151,274 @@ void Semantics::DeduceMaybeUsed() { } } -bool Semantics::CheckRvalue(Expr* expr) { - if (!CheckExpr(expr)) +ir::Value* Semantics::CheckRvalue(Expr* expr, ExprFlags flags) { + auto val = CheckExpr(expr, flags | ExprFlags::WANT_RVALUE); + if (!val) + return nullptr; + + if (auto lval = val->as()) + return BuildRvalue(expr, lval); + + return val; +} + +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); +} + +QualType Semantics::BuildRvalueType(QualType type) { + if (type->isReference()) + return QualType(type->inner()); + return type; +} + +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 CheckRvalue(expr->pos(), expr->val()); + return true; } -bool Semantics::CheckRvalue(const token_pos_t& pos, const value& val) { - if (auto accessor = val.accessor()) { - if (!accessor->getter()) { - report(pos, 149) << accessor->name(); +auto Semantics::FindUserOp(Expr* expr, int token, QualType type1, QualType type2) -> UserOp { + // No operator overloading allowed in the preprocessor. + if (cc_.in_preprocessor()) + return {}; + + // :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 {}; + + auto opername = GetOverloadName(token); + if (opername.empty()) + return {}; + + auto atom = cc_.atom(opername); + Decl* chain = FindSymbol(*sc_, atom); + if (!chain) + return {}; + + 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 {}; + + // 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); + + auto target = BuildFunction(decl); + if (!target) + return {}; + return {target, swapparams}; +} + +ir::Value* Semantics::MaybeCallUserOp(Expr* expr, int token, ir::Value* first, + ir::Value* second) +{ + // No overloads for int,int. + QualType type1 = first->type(); + QualType type2; + if (second) + type2 = second->type(); + + auto userop = FindUserOp(expr, token, type1, type2); + if (!userop.target) + return nullptr; + + return new ir::CallUserOp(expr, QualType(userop.target->decl()->return_type()), userop.target, + first, second, userop.swapparams); +} + +static void MarkUsage(ir::VariableRef* ref, uint8_t usage) { + auto var = ref->var(); + if (usage & (uWRITTEN | uMAYBE_WRITTEN)) + var->set_written(); + if (usage & uREAD) + var->set_read(); +} + +ir::Value* Semantics::BindRvalue(Expr* expr, ir::Value* val) { + auto lval = val->as(); + if (!lval) + return val; + + if (!BindLvalue(lval, uREAD)) + return nullptr; + + return BuildRvalue(expr, lval); +} + +ir::Lvalue* Semantics::BindLvalue(ir::Value* val, uint8_t usage) { + ir::Lvalue* lval = val->as(); + if (!lval) { + report(val->pn(), 455); + return nullptr; + } + if (!BindLvalue(lval, usage)) + return nullptr; + return lval; +} + +bool Semantics::BindLvalue(ir::Lvalue* lval, uint8_t usage) { + if (lval->type().is_const()) { + if (usage & uWRITTEN) { + report(lval->pn(), 22); return false; } + if (usage & uMAYBE_WRITTEN) + return false; + } + + switch (lval->kind()) { + case IrKind::VariableRef: { + auto ref = lval->to(); + MarkUsage(ref, usage); + return true; + } + case IrKind::IndexOp: { + auto op = lval->to(); + // Mark base inner variables as written. + if (auto ref = op->base()->as()) + MarkUsage(ref, usage); + return true; + } + case IrKind::FieldRef: { + auto op = lval->to(); + // Mark base inner variables as written. + if (auto ref = op->base()->as()) + MarkUsage(ref, usage); + return true; + } + case IrKind::PropertyRef: { + auto op = lval->to(); + // Mark base inner variables as written. + if (auto ref = op->val()->as()) + MarkUsage(ref, usage); + if (usage & uREAD) { + if (!op->decl()->getter()) { + report(op->pn(), 149) << op->decl()->name(); + return false; + } + auto getter = BuildFunction(op->decl()->getter()); + if (!getter) + return false; + op->BindGetter(getter); + } + if (usage & (uWRITTEN | uMAYBE_WRITTEN)) { + if (!op->decl()->setter()) { + if (usage & uWRITTEN) + report(op->pn(), 152) << op->decl()->name(); + return false; + } + auto setter = BuildFunction(op->decl()->setter()); + if (!setter) + return false; + op->BindSetter(setter); + } + return true; + } + default: + assert(false); + return false; } - return true; } -void DeleteStmt::ProcessUses(SemaContext& sc) { - expr_->MarkAndProcessUses(sc); - markusage(map_->dtor(), uREAD); +uint32_t Semantics::AllocTempSlot() { + assert(temp_slots_); + uint32_t slot = sc_->AllocTempSlot(); + temp_slots_->emplace_back(slot); + return slot; +} + +uint32_t SemaContext::AllocTempSlot() { + if (!free_local_slots_.empty()) + return ke::PopBack(&free_local_slots_); + + uint32_t next_slot = fun_->num_slots(); + fun_->set_num_slots(next_slot + 1); + return next_slot; +} + +void SemaContext::FreeTempSlot(uint32_t slot) { + assert(std::find(free_local_slots_.begin(), free_local_slots_.end(), slot) == free_local_slots_.end()); + free_local_slots_.emplace_back(slot); } } // namespace cc diff --git a/compiler/semantics.h b/compiler/semantics.h index d5518f100..f02b6f8cf 100644 --- a/compiler/semantics.h +++ b/compiler/semantics.h @@ -20,10 +20,12 @@ // 3. This notice may not be removed or altered from any source distribution. #pragma once +#include #include #include #include "compile-context.h" +#include "ir.h" #include "sc.h" #include "scopes.h" #include "parse-node.h" @@ -40,17 +42,19 @@ class SemaContext explicit SemaContext(Semantics* sema) : cc_(CompileContext::get()), sema_(sema), - func_(nullptr) + func_(nullptr), + fun_(nullptr) { cc_prev_sc_ = cc_.sema(); cc_.set_sema(this); scope_ = cc_.globals(); } - SemaContext(SemaContext& parent, FunctionDecl* func) + SemaContext(SemaContext& parent, FunctionDecl* decl) : cc_(parent.cc_), sema_(parent.sema()), scope_(parent.scope_), - func_(func), + func_(decl), + fun_(nullptr), preprocessing_(parent.preprocessing()) { cc_prev_sc_ = cc_.sema(); @@ -67,6 +71,8 @@ class SemaContext bool BindType(const token_pos_t& pos, typeinfo_t* ti); bool BindType(const token_pos_t& pos, Atom* atom, bool is_label, Type** type); + void set_fun(ir::Function* fun) { fun_ = fun; } + Stmt* void_return() const { return void_return_; } void set_void_return(Stmt* stmt) { void_return_ = stmt; } @@ -97,11 +103,13 @@ 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; } FunctionDecl* func() const { return func_; } + ir::Function* fun() const { return fun_; } Semantics* sema() const { return sema_; } SymbolScope* ScopeForAdd(); @@ -118,6 +126,10 @@ class SemaContext bool preprocessing() const { return preprocessing_; } std::unordered_set& static_scopes() { return static_scopes_; } + std::unordered_map& local_vars() { return local_vars_; } + + uint32_t AllocTempSlot(); + void FreeTempSlot(uint32_t slot); private: CompileContext& cc_; @@ -125,6 +137,7 @@ class SemaContext SymbolScope* scope_ = nullptr; AutoCreateScope* scope_creator_ = nullptr; FunctionDecl* func_ = nullptr; + ir::Function* fun_ = nullptr; Stmt* void_return_ = nullptr; bool warned_mixed_returns_ = false; bool returns_value_ = false; @@ -135,7 +148,11 @@ 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_; + std::vector free_local_slots_; + std::vector> temp_slots_; }; class Semantics final @@ -148,7 +165,7 @@ class Semantics final friend class Parser; public: - explicit Semantics(CompileContext& cc); + Semantics(CompileContext& cc, std::shared_ptr mod); bool Analyze(ParseTree* tree); @@ -157,6 +174,14 @@ class Semantics final SemaContext* context() { return sc_; } void set_context(SemaContext* sc) { sc_ = sc; } + enum class ExprFlags : unsigned int { + DEFAULT = 0, + RESULT_UNUSED = 0x1, + ALLOW_TYPES = 0x2, + WANT_RVALUE = 0x4, + ALLOW_BOUND_FUNCTIONS = 0x8, + }; + private: enum StmtFlags { STMT_DEFAULT = 0x0, @@ -171,75 +196,100 @@ class Semantics final bool CheckEnumStructDecl(EnumStructDecl* info); bool CheckFunctionDecl(FunctionDecl* info); bool CheckFunctionDeclImpl(FunctionDecl* info); + bool CheckFunctionArgument(ir::Argument* arg); 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 CheckVarDeclCommon(VarDeclBase* decl, ir::Value** init); + 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); - bool AddImplicitDynamicInitializer(VarDeclBase* decl); + ir::Value* CheckExpr(Expr* expr, ExprFlags flags); + 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, ExprFlags flags); + ir::Value* CheckIndexExpr(IndexExpr* expr); + ir::Value* CheckCallExpr(CallExpr* expr); + ir::Value* CheckSymbolExpr(SymbolExpr* expr, ExprFlags flags); + ir::Value* CheckSizeofExpr(SizeofExpr* expr); + ir::Value* CheckCastExpr(CastExpr* expr, ExprFlags flags); + ir::Value* CheckIncDecExpr(IncDecExpr* expr, ExprFlags flags); + 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, ExprFlags flags); + ir::Value* CheckStaticFieldAccessExpr(FieldAccessExpr* expr, ir::Value* base); + ir::Value* CheckEnumStructFieldAccessExpr(FieldAccessExpr* expr, ir::Value* base, + EnumStructDecl* root, ExprFlags); + ir::Value* CheckRvalue(Expr* expr, ExprFlags flags = ExprFlags::DEFAULT); + + // Check an ir::Value as conforming to an l/r-value, binding IR as needed. + // + // If |out_usage| is non-null, errors are not reported. Instead the effective + // usage is returned and the l-value is bound as possible. + ir::Value* BindRvalue(Expr* expr, ir::Value* val); + ir::Lvalue* BindLvalue(ir::Value* val, uint8_t usage); + bool BindLvalue(ir::Lvalue* val, uint8_t usage); + + // Manually build an r-value. These are infallible. Note that BuildRvalue + // can ONLY be called if BindLvalue has been called. Failure to do so will + // cause codegen assertions. + 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); + + bool CheckAssignmentLHS(BinaryExpr* expr, ir::Lvalue* lval); + bool CheckAssignmentRHS(BinaryExpr* expr, ir::Lvalue* lval, ir::Value* rval); + ir::Value* BuildImplicitDynamicInitializer(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); + bool CheckArrayDeclaration(VarDeclBase* decl, ir::Value** new_init); + ir::Value* CheckNewArrayExprForArrayInitializer(NewArrayExpr* expr); + ir::Value* CheckArgument(CallExpr* call, ArgDecl* arg, Expr* expr, ParamState* ps, + unsigned int argpos); + ir::FunctionRef* BindNewTarget(Expr* target); + ir::FunctionRef* BindCallTarget(CallExpr* call, Expr* target); void NeedsHeapAlloc(Expr* expr); void AssignHeapOwnership(ParseNode* node); - Expr* AnalyzeForTest(Expr* expr); + ir::Value* AnalyzeForTest(Expr* expr); + ir::Function* BuildFunction(FunctionDecl* decl); + + struct UserOp { + ir::Function* target = nullptr; + bool swapparams = false; + }; + UserOp FindUserOp(Expr* expr, int token, QualType first, QualType second); + ir::Value* MaybeCallUserOp(Expr* expr, int token, ir::Value* first, ir::Value* second); void DeduceLiveness(); void DeduceMaybeUsed(); @@ -250,16 +300,28 @@ class Semantics final void CheckVoidDecl(const declinfo_t* decl, int variable); bool CheckScalarType(Expr* expr); + bool CheckScalarType(Expr* expr, QualType type); + + uint32_t AllocTempSlot(); private: CompileContext& cc_; TypeManager* types_ = nullptr; tr::unordered_set static_scopes_; tr::vector maybe_used_; + std::optional global_sc_; 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; + std::vector* temp_slots_ = nullptr; }; +KE_DEFINE_ENUM_OPERATORS(Semantics::ExprFlags) + class AutoEnterScope final { public: 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..3162e5973 100644 --- a/compiler/types.h +++ b/compiler/types.h @@ -306,6 +306,11 @@ class Type : public PoolObject return pstruct_ptr_; } + // Reference here refers to heap allocation, versus value types. + bool isReferenceType() const { + return isArray() || isEnumStruct(); + } + Type* inner() const { assert(isReference() || isArray()); return inner_type_; @@ -439,6 +444,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/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"