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..441980649 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_; @@ -96,7 +96,7 @@ bool ArrayTypeResolver::Resolve() { assert(!type_->resolved_array); if (type_->type->isVoid()) { - report(pos_, 145); + report(pos_, 39); return false; } @@ -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..8ffaae427 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(IncDecUserOp) \ + 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..6ce0a38ce 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,13 +132,64 @@ void CodeGenerator::AddDebugSymbols(tr::vector* list) { } } -void -CodeGenerator::EmitStmtList(StmtList* list) -{ - for (const auto& stmt : list->stmts()) - EmitStmt(stmt); +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::EmitStmt(Stmt* stmt) { @@ -238,6 +299,7 @@ CodeGenerator::EmitStmt(Stmt* stmt) if (stmt->tree_has_heap_allocs()) LeaveHeapScope(); } +#endif void CodeGenerator::EmitChangeScopeNode(ChangeScopeNode* node) @@ -269,30 +331,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 +372,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 +406,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 +446,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 +461,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,129 +506,255 @@ 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 (expr->kind()) { - case ExprKind::UnaryExpr: - EmitUnary(expr->to()); + switch (val->kind()) { + case IrKind::ConstVal: + EmitConst(val->to()); break; - case ExprKind::IncDecExpr: - EmitIncDec(expr->to()); + case IrKind::UnaryOp: + EmitUnary(val->to()); break; - case ExprKind::BinaryExpr: - EmitBinary(expr->to()); + case IrKind::Load: + EmitLoad(val->to()); break; - case ExprKind::LogicalExpr: - EmitLogicalExpr(expr->to()); + case IrKind::CommaOp: + EmitCommaOp(val->to()); break; - case ExprKind::ChainedCompareExpr: - EmitChainedCompareExpr(expr->to()); + case IrKind::CallOp: + EmitCallOp(val->to()); break; - case ExprKind::TernaryExpr: - EmitTernaryExpr(expr->to()); + case IrKind::BinaryOp: + EmitBinary(val->to()); break; - case ExprKind::CastExpr: - EmitExpr(expr->to()->expr()); + case IrKind::CharArrayLiteral: + EmitCharArrayLiteral(val->to()); break; - case ExprKind::SymbolExpr: - EmitSymbolExpr(expr->to()); + case IrKind::TernaryOp: + EmitTernaryOp(val->to()); break; - case ExprKind::RvalueExpr: { - auto e = expr->to(); - EmitExpr(e->expr()); - value val = e->expr()->val(); - EmitRvalue(&val); + case IrKind::Store: + EmitStore(val->to()); break; - } - case ExprKind::CommaExpr: { - auto ce = expr->to(); - for (const auto& expr : ce->exprs()) - EmitExpr(expr); + case IrKind::StoreWithTemp: + EmitStoreWithTemp(val->to()); break; - } - case ExprKind::ThisExpr: { - auto e = expr->to(); - if (e->decl()->type()->isEnumStruct()) - __ address(e->decl(), sPRI); + case IrKind::FunctionRef: + EmitFunctionRef(val->to()); break; - } - case ExprKind::StringExpr: { - auto se = expr->to(); - auto addr = data_.dat_address(); - data_.Add(se->text()->chars(), se->text()->length()); - __ const_pri(addr); + case IrKind::AddressOf: + EmitAddressOf(val->to()); break; - } - case ExprKind::ArrayExpr: { - auto e = expr->to(); - auto addr = data_.dat_address(); - for (const auto& expr : e->exprs()) - data_.Add(expr->val().constval()); - __ const_pri(addr); + case IrKind::CallUserOp: + EmitCallUserOp(val->to()); break; - } - case ExprKind::IndexExpr: - EmitIndexExpr(expr->to()); + case IrKind::IncDec: + case IrKind::IncDecUserOp: + EmitIncDec(val->to()); + break; + case IrKind::TempValueRef: + EmitTempValueRef(val->to()); break; - case ExprKind::FieldAccessExpr: - EmitFieldAccessExpr(expr->to()); + case IrKind::CastOp: { + auto inner = val->to()->val(); + EmitValue(inner); break; - case ExprKind::CallExpr: - EmitCallExpr(expr->to()); + } + default: + assert(false); break; - case ExprKind::DefaultArgExpr: - EmitDefaultArgExpr(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 ExprKind::CallUserOpExpr: - EmitCallUserOpExpr(expr->to()); + } + case IrKind::IndexOp: { + auto index = lval->to(); + EmitAddressOfIndexOp(index); + + if (index->type()->isArray()) + __ emit(OP_LOAD_I); break; - case ExprKind::NewArrayExpr: - EmitNewArrayExpr(expr->to()); + } + case IrKind::StackRef: { + auto op = lval->to(); + __ emit(OP_ADDR_PRI, GetSlotAddr(op->slot())); break; - case ExprKind::NamedArgExpr: - EmitExpr(expr->to()->expr); + } + case IrKind::FieldRef: { + auto op = lval->to(); + EmitAddressOfFieldRef(op); break; - + } default: assert(false); } } -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::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); + if (auto user_op = incdec->as()) { + if (postfix) { + if (save_pri) { + // Re-order stack so that the saved address is popped first. + // Note, SWAP doesn't quite do what we want here. + __ emit(OP_POP_ALT); + __ emit(OP_PUSH_PRI); + __ emit(OP_PUSH_ALT); + } else { + __ emit(OP_PUSH_PRI); + } + } + // Push the value to increment. + __ emit(OP_PUSH_PRI); + EmitCall(user_op->target(), 1); + if (save_pri) + __ emit(OP_POP_ALT); + } else { + 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++; + } + + EmitCall(target->fun(), nargs); + + assert(!target->fun()->decl()->return_array()); +} + +void CodeGenerator::EmitFunctionRef(ir::FunctionRef* ref) { + __ emit(OP_CONST_PRI, &ref->fun()->public_id()); +} + +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 +762,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 +780,332 @@ 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: { + auto op = lval->to(); + if (!(op->type()->isArray() || op->type()->isEnumStruct())) + __ 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 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); + } - auto tmp = left_val; - if (expr->assignop().sym) - EmitUserOp(expr->assignop(), nullptr); - EmitStore(&tmp); + 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()); - UserOperation user_op = in_user_op; + if (ref->field()->offset()) { + __ const_alt(ref->field()->offset() * sizeof(cell_t)); + __ emit(OP_ADD); + } +} - // 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) + break; + } + + case IrKind::IndexOp: { + auto op = lval->to(); + if (op->base()->type()->isCharArray()) + __ emit(OP_STRB_I, 1); + else + __ emit(OP_STOR_I); + break; + } + + case IrKind::FieldRef: + __ emit(OP_STOR_I); + break; + + case IrKind::PropertyRef: { + auto ref = lval->to(); + assert(ref->setter()); + + if (save_rval) __ emit(OP_PUSH_PRI); - EmitExpr(right); - if (must_save_lhs) - __ emit(OP_POP_ALT); + __ emit(OP_PUSH_PRI); + __ emit(OP_PUSH_ALT); + EmitCall(ref->setter(), 2); + if (save_rval) + __ emit(OP_POP_PRI); + break; } + + default: + assert(false); } +} - if (oper_tok) { - if (user_op.sym) { - EmitUserOp(user_op, nullptr); +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; } - 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); - } } + out->emplace_back(op); } -void -CodeGenerator::EmitLogicalExpr(LogicalExpr* expr) -{ - bool jump_on_true = expr->token() == tlOR; - - Label shortcircuit, done; - - EmitTest(expr, jump_on_true, &shortcircuit); - __ const_pri(!jump_on_true); - __ emit(OP_JUMP, &done); - __ bind(&shortcircuit); - __ const_pri(jump_on_true); - __ bind(&done); -} +void CodeGenerator::EmitLogicalExprTest(ir::BinaryOp* root, bool jump_on_true, sp::Label* target) { + assert(root->token() == tlAND || root->token() == tlOR); -void -CodeGenerator::EmitLogicalExprTest(LogicalExpr* root, bool jump_on_true, Label* target) -{ - std::vector sequence; - root->FlattenLogical(root->token(), &sequence); + std::vector sequence; + FlattenLogical(root->left(), root->token(), &sequence); + FlattenLogical(root->right(), root->token(), &sequence); // a || b || c .... given jumpOnTrue, should be: // @@ -923,44 +1151,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); } -static inline OPCODE -CmpTokenToOp(int token) -{ - switch (token) { - case tlGE: - return OP_JSGEQ; - case tlLE: - return OP_JSLEQ; - case '<': - return OP_JSLESS; - case '>': - return OP_JSGRTR; - default: - assert(false); - return OP_HALT; - } -} - +#if 0 bool CodeGenerator::EmitChainedCompareExprTest(ChainedCompareExpr* root, bool jump_on_true, Label* target) @@ -982,161 +1192,50 @@ CodeGenerator::EmitChainedCompareExprTest(ChainedCompareExpr* root, bool jump_on if (!jump_on_true) { switch (token) { case '<': - token = tlGE; - break; - case '>': - token = tlLE; - break; - case tlGE: - token = '<'; - break; - 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); + token = tlGE; + break; + case '>': + token = tlLE; + break; + case tlGE: + token = '<'; + break; + case tlLE: + token = '>'; + break; + 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++; } } +#endif +#if 0 void CodeGenerator::EmitCallExpr(CallExpr* call) { // If returning an array, push a hidden parameter. if (call->fun()->return_array()) { @@ -1231,21 +1330,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); + + if (op->second()) { + assert(op->target()->argv().size() == 2); - const auto& userop = expr->userop(); - if (userop.oper) { - auto val = expr->expr()->val(); - EmitUserOp(userop, &val); + // 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 +1418,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 +1486,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 +1504,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 +1531,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 +1569,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 +1581,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 +1625,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 +1651,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 +1682,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 +1714,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 +1729,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 +1739,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,98 +1769,106 @@ 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 - EmitStmt(info->body()); + 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()); + + 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()); } -void -CodeGenerator::EmitEnumStructDecl(EnumStructDecl* decl) -{ - for (const auto& fun : decl->methods()) - EmitFunctionDecl(fun); -} - -void -CodeGenerator::EmitMethodmapDecl(MethodmapDecl* decl) -{ - for (const auto& prop : decl->properties()) { - if (prop->getter()) - EmitFunctionDecl(prop->getter()); - if (prop->setter()) - EmitFunctionDecl(prop->setter()); - } - for (const auto& method : decl->methods()) - EmitFunctionDecl(method); -} - -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 +1876,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 +1981,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 +2062,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 +2099,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 +2143,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 +2158,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 +2171,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 +2183,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 +2195,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..7a2d57266 --- /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::IncDecUserOp: + 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..d9e31bbf3 --- /dev/null +++ b/compiler/ir.h @@ -0,0 +1,987 @@ +// 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::IncDecUserOp; + } + + 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 IncDecUserOp final : public IncDec { + public: + IncDecUserOp(IncDecExpr* expr, QualType type, Lvalue* lval, Function* target, + bool used) + : IncDec(IrKind::IncDecUserOp, expr, type, lval, used), + target_(target) + {} + + Function* target() const { return target_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::IncDecUserOp; } + + private: + Function* target_; +}; + +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 e3600e8ab..177604dd0 100644 --- a/compiler/messages.h +++ b/compiler/messages.h @@ -56,14 +56,14 @@ 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", /*036*/ "empty statement\n", /*037*/ "invalid string (possibly non-terminated string)\n", /*038*/ "extra characters on line\n", - /*039*/ "unused39\n", + /*039*/ "void is not a valid array type\n", /*040*/ "duplicate \"case\" label (value %d)\n", /*041*/ "invalid ellipsis, array size is not known\n", /*042*/ "invalid combination of class specifiers\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 222c5d96a..cec07fe8d 100644 --- a/compiler/name-resolution.cpp +++ b/compiler/name-resolution.cpp @@ -201,12 +201,20 @@ 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); - else - error(field->pos(), 80); + if (field->value() && field->value()->Bind(sc)) { + auto val = sc.sema()->CheckRvalue(field->value()); + if (!val) + return false; + + auto cv = val->as(); + if (!cv) { + report(field->value(), 8); + return false; + } + + matchtag(type_, *cv->type(), MATCHTAG_COERCE | MATCHTAG_ENUM_ASSN); + + value = cv->value(); } field->set_type(type_); @@ -368,9 +376,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 +385,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 +410,24 @@ bool VarDeclBase::Bind(SemaContext& sc) { if (!sc.BindType(pos(), &type_)) return false; - if (!type_.dim_exprs.empty()) { - if (!ResolveArrayType(sc.sema(), this)) - return false; - } - - if (type()->isVoid()) - error(pos_, 144); + if (type_.type->isVoid()) + report(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); + bool ok = true; + if (!type_.dim_exprs.empty()) { + 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,33 +801,24 @@ 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; - } +#if 0 Type* type = typeinfo.type; +#endif + + if (var->init()) + var->init()->Bind(sc); - /* 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 0 + if (type->isArray() || type->isEnumStruct()) { + assert(false); if (sc.sema()->CheckVarDecl(var) && var->init_rhs()) fill_arg_defvalue(sc.cc(), var); } else { @@ -846,25 +830,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()) @@ -892,6 +877,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..b586ef323 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,21 @@ 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(); + if (type) + *type = cv->type(); + return true; + } + + return Expr::EvalConst(expr, val, type); } Stmt* @@ -1587,9 +1600,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..f88d8cab1 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,92 @@ 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; + // Always map the variable so that symbol binding works. + ir::Value* init; + bool ok = CheckVarDeclCommon(decl, &init); + // :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 ok; +} + +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->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 +381,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 +397,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 +413,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 +422,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 +439,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 +521,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 +576,95 @@ 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) { + assert(!userop.swapparams); + return new ir::IncDecUserOp(incdec, type, lval, userop.target, 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 +690,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 it's an outparam, also mark it as read. - if (sym->vclass() == sARGUMENT && - (sym->type()->isReference() || sym->type()->isArray())) + if (!CheckAssignmentLHS(expr, lval)) + return nullptr; + + 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)); + ir::Value* out = nullptr; - const auto& left_val = left->val(); - const auto& right_val = right->val(); - - 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()); + if (!out) + TypeChecker::DoCoerce(expr->pos(), *left->type(), *right->type(), TypeChecker::Commutative); - 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(); - - 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; + + auto load = rval->as(); + if (!load) + 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 right_var = load->lval()->as(); + if (!right_var) + return; + + if (left_var->var() == right_var->var()) + report(expr, 226) << left_var->var()->decl()->name(); +} + +bool Semantics::CheckAssignmentRHS(BinaryExpr* expr, ir::Lvalue* lval, ir::Value* rval) { + CheckSelfAssignment(expr, lval, rval); - if (auto left_array = left_val.type()->as()) { - TypeChecker tc(expr, left_val.type(), right_val.type(), TypeChecker::Assignment); + if (auto left_array = lval->type()->as()) { + TypeChecker tc(expr, lval->type(), rval->type(), TypeChecker::Assignment); if (!tc.Coerce()) return false; - auto right_array = right_val.type()->to(); + auto right_array = rval->type()->to(); if (right_array->inner()->isArray()) { report(expr, 23); return false; @@ -891,25 +860,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 +889,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 +901,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 +958,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 +992,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 +1060,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 +1108,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 +1136,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 +1185,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 +1229,61 @@ 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); + 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 +1292,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 +1310,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; + + values.emplace_back(val); - const auto& val = expr->val(); - if (val.ident != iCONSTEXPR) { + 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 +1386,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; - auto& val = expr->val(); - val.ident = sym->ident(); - val.sym = sym; - val.set_type(sym->type()); - expr->set_lvalue(true); - return true; + var->set_read(); + + // |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 +1492,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 +1536,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 +1549,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 +1557,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 +1579,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::BoundFunction(expr, QualType(fun->return_type()), base, 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 +1717,60 @@ 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++; + ParamState ps; + + unsigned int implicit_args = 0; + if (auto bf = ref->as()) { + implicit_args++; + ps.argv.emplace_back(bf->val()); } + unsigned int argidx = implicit_args; + unsigned int nargs = implicit_args; + bool namedparams = false; + auto& arglist = decl->args(); 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 +1778,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 +1811,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 +1874,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 +1974,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 +1994,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 +2046,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 +2057,7 @@ bool Semantics::CheckNewArrayExprForArrayInitializer(NewArrayExpr* na) { assert(na->type()->isArray()); na->set_analysis_result(true); - return true; +#endif } void @@ -2259,22 +2068,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 +2113,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 +2221,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 +2243,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 +2263,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 +2280,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 +2293,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 +2349,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 +2382,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 +2432,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 +2457,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 +2504,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 +2539,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 +2569,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 +2626,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 +2691,31 @@ 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()); + } + + auto fwd = info->prototype(); + if (fwd && fwd->deprecate() && !info->is_stock() && fwd != info) + report(info->pos(), 234) << info->name() << fwd->deprecate(); + +#if 0 if (info->is_native()) { if (decl.type.dim_exprs.size() > 0) { report(info->pos(), 83); @@ -2875,10 +2736,6 @@ bool Semantics::CheckFunctionDeclImpl(FunctionDecl* info) { if (info->as()) maybe_used_.emplace_back(info); - auto fwd = info->prototype(); - if (fwd && fwd->deprecate() && !info->is_stock()) - report(info->pos(), 234) << info->name() << fwd->deprecate(); - bool ok = CheckStmt(body, STMT_OWNS_HEAP); info->set_returns_value(sc_->returns_value()); @@ -2916,6 +2773,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 +2977,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 +3062,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 +3090,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 +3144,270 @@ 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; + return BindRvalue(expr, 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/tests/compile-only/fail-bad-void-2.txt b/tests/compile-only/fail-bad-void-2.txt index 65cf47cb4..636a53c3b 100644 --- a/tests/compile-only/fail-bad-void-2.txt +++ b/tests/compile-only/fail-bad-void-2.txt @@ -1 +1 @@ -(1) : error 145: invalid type expression +(1) : error 039: void is not a valid array type 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"