From baa55bb48251e9071de15c56bd6ed0ccb9d79d2b Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 29 Jul 2024 21:25:24 -0700 Subject: [PATCH] WIP: Generate an IR. The compiler has come a long way, but we still can't do int64, because it requires a non-cell storage size. There's no (sane) way to express conversions between int32 and int64 within the AST, because we have no uniform way of inserting conversion nodes. This is already a deep problem that has been hacked around for operator overloads and property accessors, and it doesn't scale. The solution is obvious: transform the AST into an IR. That's what we should have done from the beginning but didn't. Unfortunately it requires a *lot* of refactoring and a ton of boilerplate. So far, I have most of the boilerplate done, but the refactoring is only halfway there. CodeGenerator has not been ported to the IR yet. Once this gigantic patch is done, we'll have the following changes: - `struct value` will be eliminated, and good riddance. - The AST will be immutable after parsing. - The semantic analysis phase will output a new IR tree. - CodeGenerator will generate off the IR instead. Since the IR is a transformation of the AST, I'm expecting minimal changes to the end result. - functag_t will be replaced by FunctionType. V2: CG-IR can now assemble trivial programs. V3: CG-IR supports basic calls; 341 test failures. V4: CG-IR supports binary ops; 333 test failures. V5: CG-IR supports do-while and if; 329 test failures. V6: CG-IR supports args, local incdec, switch; 319 test failures. V7: Dropped IncDecOp in favor of Store/BinaryOp primitives. Added global variable support. V8: Added support for heap scopes. 294 test failures. V9: Add support for Load(IndexOp) and arrays as arguments. 290 test failures. V10: Add support for varargs. 289 test failures. V11: Port array helpers to IR. 280 test failures. --- compiler/AMBuilder | 1 + compiler/array-helpers.cpp | 308 ++--- compiler/array-helpers.h | 13 +- compiler/assembler.cpp | 102 +- compiler/assembler.h | 1 + compiler/ast-types.h | 58 + compiler/code-generator.cpp | 1145 +++++++++--------- compiler/code-generator.h | 67 +- compiler/compile-context.h | 7 +- compiler/expressions.cpp | 174 +-- compiler/expressions.h | 8 +- compiler/ir.cpp | 116 ++ compiler/ir.h | 851 +++++++++++++ compiler/lexer.cpp | 5 +- compiler/lexer.h | 2 +- compiler/main.cpp | 10 +- compiler/messages.h | 7 +- compiler/name-resolution.cpp | 61 +- compiler/parse-node.cpp | 17 - compiler/parse-node.h | 38 +- compiler/parser.cpp | 8 +- compiler/parser.h | 2 +- compiler/pool-objects.h | 11 +- compiler/qualtype.h | 9 +- compiler/semantics.cpp | 2055 ++++++++++++++++---------------- compiler/semantics.h | 125 +- compiler/smx-assembly-buffer.h | 19 +- compiler/type-checker.cpp | 6 +- compiler/type-checker.h | 1 + compiler/types.h | 15 + shared/string-pool.h | 48 +- third_party/amtl | 2 +- vm/method-verifier.cpp | 1 + vm/opcodes.cpp | 2 - 34 files changed, 3083 insertions(+), 2212 deletions(-) create mode 100644 compiler/ir.cpp create mode 100644 compiler/ir.h diff --git a/compiler/AMBuilder b/compiler/AMBuilder index 8a83a8819..137a495e5 100644 --- a/compiler/AMBuilder +++ b/compiler/AMBuilder @@ -12,6 +12,7 @@ module.sources += [ 'data-queue.cpp', 'errors.cpp', 'expressions.cpp', + 'ir.cpp', 'lexer.cpp', 'main.cpp', 'name-resolution.cpp', diff --git a/compiler/array-helpers.cpp b/compiler/array-helpers.cpp index 122386b0b..4f6149239 100644 --- a/compiler/array-helpers.cpp +++ b/compiler/array-helpers.cpp @@ -394,14 +394,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 +415,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 +454,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 +488,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 +498,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 +515,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 +550,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 +630,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; + return nullptr; if (init->val().ident != iCONSTEXPR) { report(init->pos(), 47); - return false; + return nullptr; } report(init->pos(), 241); @@ -634,17 +651,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 +672,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_->CheckExpr(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 +753,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_->CheckExpr(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 +796,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 +811,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 +883,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 +902,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 +919,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 +928,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 +952,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 +984,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 +1019,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 +1062,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 +1087,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..58411761f 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,52 @@ 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(TempRef) \ + FOR_EACH(PropertyRef) \ + FOR_EACH(FieldRef) \ + FOR_EACH(UnaryOp) \ + FOR_EACH(CallUserOp) \ + FOR_EACH(IncDecOp) \ + FOR_EACH(Store) \ + FOR_EACH(ThisRef) \ + FOR_EACH(TempAddr) \ + FOR_EACH(GetAddressOp) \ + FOR_EACH(ArrayInitializer) + +namespace sp { +namespace cc { + enum class ExprKind : uint8_t { #define _(Name) Name, @@ -92,3 +140,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..fe877090f 100644 --- a/compiler/code-generator.cpp +++ b/compiler/code-generator.cpp @@ -41,9 +41,9 @@ namespace cc { #define __ asm_. -CodeGenerator::CodeGenerator(CompileContext& cc, ParseTree* tree) +CodeGenerator::CodeGenerator(CompileContext& cc, std::shared_ptr mod) : cc_(cc), - tree_(tree) + mod_(mod) { } @@ -51,7 +51,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 +81,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 +91,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 +113,12 @@ void CodeGenerator::AddDebugSymbol(Decl* decl, uint32_t pc) { asm_.position(), decl->ident(), decl->vclass(), (int)decl->is_const()); if (fun_) { +#if 0 auto data = fun_->cg(); if (!data->dbgstrs) data->dbgstrs = cc_.NewDebugStringList(); data->dbgstrs->emplace_back(string.c_str(), string.size()); +#endif } else { debug_strings_.emplace_back(string.c_str(), string.size()); } @@ -122,6 +131,58 @@ void CodeGenerator::AddDebugSymbols(tr::vector* list) { } } +void CodeGenerator::EmitBlock(ir::InsnBlock* block) { + if (block->has_heap_allocs()) + EnterHeapScope(Flow_None); + + for (auto iter = block->list(); iter; iter = iter->next()) + EmitInsn(iter); + + if (block->has_heap_allocs()) + LeaveHeapScope(); +} + +void CodeGenerator::EmitInsn(ir::Insn* node) { + switch (node->kind()) { + case IrKind::Return: + EmitReturn(node->as()); + break; + case IrKind::Variable: + EmitVarDecl(node->as()); + break; + case IrKind::ValueInsn: + EmitValueInsn(node->as()); + break; + case IrKind::DoWhile: + EmitDoWhile(node->as()); + break; + case IrKind::If: + EmitIf(node->as()); + break; + case IrKind::Break: + EmitLoopControl(tBREAK); + break; + case IrKind::Continue: + EmitLoopControl(tCONTINUE); + break; + case IrKind::Switch: + EmitSwitch(node->as()); + break; + default: + assert(false); + } +} + +void CodeGenerator::EmitValueInsn(ir::ValueInsn* insn) { + EmitValue(insn->val()); +} + +void CodeGenerator::EmitCommaOp(ir::CommaOp* op) { + for (const auto& val : op->values()) + EmitValue(val); +} + +#if 0 void CodeGenerator::EmitStmtList(StmtList* list) { @@ -238,6 +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,21 +506,135 @@ CodeGenerator::EmitPstruct(VarDeclBase* decl) for (const auto& value : values) data_.Add(value); } +#endif -void -CodeGenerator::EmitExpr(Expr* expr) -{ - AutoErrorPos aep(expr->pos()); +void CodeGenerator::EmitValue(ir::Value* val) { + AutoErrorPos aep(val->pn()->pos()); - if (expr->val().ident == iCONSTEXPR) { - __ const_pri(expr->val().constval()); - return; + switch (val->kind()) { + case IrKind::ConstVal: + EmitConst(val->to()); + break; + case IrKind::UnaryOp: + EmitUnary(val->to()); + break; + case IrKind::Load: + EmitLoad(val->to()); + break; + case IrKind::CommaOp: + EmitCommaOp(val->to()); + break; + case IrKind::CallOp: + EmitCallOp(val->to()); + break; + case IrKind::BinaryOp: + EmitBinary(val->to()); + break; + case IrKind::CharArrayLiteral: + EmitCharArrayLiteral(val->to()); + break; + case IrKind::TernaryOp: + EmitTernaryOp(val->to()); + break; + case IrKind::Store: + EmitStore(val->to()); + break; + case IrKind::FunctionRef: + EmitFunctionRef(val->to()); + break; + default: + assert(false); + break; } +} - switch (expr->kind()) { - case ExprKind::UnaryExpr: - EmitUnary(expr->to()); +void CodeGenerator::EmitLoad(ir::Load* load) { + auto lval = load->lval(); + switch (lval->kind()) { + case IrKind::VariableRef: + EmitLoadVariable(lval->to()); break; + case IrKind::IndexOp: + EmitLoadIndexOp(lval->to()); + break; + default: + assert(false); + } +} + +void CodeGenerator::EmitStore(ir::Store* op) { + auto lval = op->lval(); + bool save_pri = EmitPrecalcLvalue(lval); + if (save_pri) + __ emit(OP_PUSH_PRI); + + EmitValue(op->val()); + + if (save_pri) { + assert(false); + __ emit(OP_POP_ALT); + if (lval->type()->isCharArray()) + __ emit(OP_STRB_I); + else + __ emit(OP_STOR_I); + } else { + 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 { + __ emit(OP_STOR_S_PRI, var->addr()); + } + break; + } + default: + assert(false); + } + } +} + +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 { + __ emit(OP_LOAD_PRI, var->addr()); + } +} + +void CodeGenerator::EmitCallOp(ir::CallOp* call) { + auto target = call->target(); + assert(!target->decl()->return_array()); + + const auto& argv = call->argv(); + for (size_t i = argv.size() - 1; i < argv.size(); i--) { + EmitValue(argv[i]); + __ emit(OP_PUSH_PRI); + } + + EmitCall(target, (cell_t)argv.size()); + + assert(!target->decl()->return_array()); +} + +void CodeGenerator::EmitFunctionRef(ir::FunctionRef* ref) { + __ emit(OP_CONST_PRI, &ref->fun()->public_id()); +} + +#if 0 +void +CodeGenerator::EmitExpr(Expr* expr) +{ + switch (expr->kind()) { case ExprKind::IncDecExpr: EmitIncDec(expr->to()); break; @@ -526,40 +709,48 @@ CodeGenerator::EmitExpr(Expr* expr) EmitNewArrayExpr(expr->to()); break; case ExprKind::NamedArgExpr: - EmitExpr(expr->to()->expr); + EmitExpr(expr->to()->expr()); break; default: assert(false); } } +#endif -void -CodeGenerator::EmitTest(Expr* expr, bool jump_on_true, Label* target) -{ - switch (expr->kind()) { - case ExprKind::LogicalExpr: - EmitLogicalExprTest(expr->to(), jump_on_true, target); - return; - case ExprKind::UnaryExpr: - if (EmitUnaryExprTest(expr->to(), jump_on_true, target)) +void CodeGenerator::EmitConst(ir::Const* cv) { + __ const_pri(cv->value()); +} + +static inline ir::BinaryOp* ToLogical(ir::Value* op) { + if (auto bin = op->as()) { + if (bin->token() == tlAND || bin->token() == tlOR) + return bin; + } + return nullptr; +} + +void CodeGenerator::EmitTest(ir::Value* test, bool jump_on_true, sp::Label* target) { + switch (test->kind()) { + case IrKind::UnaryOp: + if (EmitUnaryExprTest(test->to(), jump_on_true, target)) + return; + break; + case IrKind::BinaryOp: + if (auto bin = ToLogical(test)) { + EmitLogicalExprTest(bin->to(), jump_on_true, target); return; + } break; +#if 0 case ExprKind::ChainedCompareExpr: if (EmitChainedCompareExprTest(expr->to(), jump_on_true, target)) return; break; - case ExprKind::CommaExpr: { - auto ce = expr->to(); - for (size_t i = 0; i < ce->exprs().size() - 1; i++) - EmitExpr(ce->exprs().at(i)); - - EmitTest(ce->exprs().back(), jump_on_true, target); - return; - } +#endif } - EmitExpr(expr); + EmitValue(test); if (jump_on_true) __ emit(OP_JNZ, target); @@ -567,17 +758,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 +776,164 @@ 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); - } - } 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. - __ emit(OP_PUSH_PRI); - EmitCall(userop.sym, 1); - // Restore the address and emit the store. - __ 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); - } + 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; } -} -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) { + if (auto left_cv = op->left()->as()) { + if (auto right_cv = op->right()->as()) + __ const_pri(right_cv->value()); + else + EmitValue(op->right()); + __ const_alt(left_cv->value()); + } else { + EmitValue(op->left()); + + if (auto right_cv = op->right()->as()) { + if (IsOperTokenCommutative(op->token())) { + __ const_alt(right_cv->value()); + } else { __ emit(OP_PUSH_PRI); - EmitRvalue(left_val); - saved_lhs = true; + __ const_pri(right_cv->value()); + __ emit(OP_POP_ALT); } - } else if (left_val.ident == iACCESSOR) { - __ emit(OP_PUSH_PRI); - if (oper) - EmitRvalue(left_val); - saved_lhs = true; } else { - assert(left->lvalue()); - if (oper) - EmitRvalue(left_val); - } - - if (expr->array_copy_length()) { - assert(!oper); - assert(!expr->assignop().sym); - __ emit(OP_PUSH_PRI); - EmitExpr(right); + EmitValue(op->right()); __ emit(OP_POP_ALT); - __ emit(OP_MOVS, expr->array_copy_length() * sizeof(cell)); - return; } } - assert(!expr->array_copy_length()); - assert(!left_val.type()->isArray()); - - EmitBinaryInner(oper, expr->userop(), left, right); - - if (IsAssignOp(token)) { - if (saved_lhs) - __ emit(OP_POP_ALT); + 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; - auto tmp = left_val; - if (expr->assignop().sym) - EmitUserOp(expr->assignop(), nullptr); - EmitStore(&tmp); + default: + assert(false); } } -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::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); +} - UserOperation user_op = in_user_op; +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); +} - // left goes into ALT, right goes into PRI, though we can swap them for - // commutative operations. - if (left_val.ident == iCONSTEXPR) { - if (right_val.ident == iCONSTEXPR) - __ const_pri(right_val.constval()); - else - EmitExpr(right); - __ const_alt(left_val.constval()); - } else { - // If performing a binary operation, we need to make sure the LHS winds - // up in ALT. If performing a store, we only need to preserve LHS to - // ALT if it can't be re-evaluated. - bool must_save_lhs = oper_tok || !left_val.canRematerialize(); - if (right_val.ident == iCONSTEXPR) { - if (commutative(oper_tok)) { - __ const_alt(right_val.constval()); - user_op.swapparams ^= true; - } else { - if (must_save_lhs) - __ emit(OP_PUSH_PRI); - __ const_pri(right_val.constval()); - if (must_save_lhs) - __ emit(OP_POP_ALT); - } - } else { - if (must_save_lhs) - __ emit(OP_PUSH_PRI); - EmitExpr(right); - if (must_save_lhs) - __ emit(OP_POP_ALT); - } +bool CodeGenerator::EmitPrecalcLvalue(ir::Lvalue* lval) { + switch (lval->kind()) { + case IrKind::VariableRef: + return false; + + default: + assert(false); + return true; } +} - 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; +void CodeGenerator::EmitLogicalExprTest(ir::BinaryOp* root, bool jump_on_true, sp::Label* target) { + assert(root->token() == tlAND || root->token() == tlOR); - EmitTest(expr, jump_on_true, &shortcircuit); - __ const_pri(!jump_on_true); - __ emit(OP_JUMP, &done); - __ bind(&shortcircuit); - __ const_pri(jump_on_true); - __ bind(&done); -} - -void -CodeGenerator::EmitLogicalExprTest(LogicalExpr* root, bool jump_on_true, Label* target) -{ - std::vector sequence; - root->FlattenLogical(root->token(), &sequence); + std::vector sequence; + FlattenLogical(root->left(), root->token(), &sequence); + FlattenLogical(root->right(), root->token(), &sequence); // a || b || c .... given jumpOnTrue, should be: // @@ -923,26 +979,26 @@ CodeGenerator::EmitLogicalExprTest(LogicalExpr* root, bool jump_on_true, Label* Label fallthrough; for (size_t i = 0; i < sequence.size() - 1; i++) { - auto expr = sequence.at(i); + auto op = sequence.at(i); if (root->token() == tlOR) { if (jump_on_true) - EmitTest(expr, true, target); + EmitTest(op, true, target); else - EmitTest(expr, true, &fallthrough); + EmitTest(op, true, &fallthrough); } else { assert(root->token() == tlAND); if (jump_on_true) - EmitTest(expr, false, &fallthrough); + EmitTest(op, false, &fallthrough); else - EmitTest(expr, false, target); + EmitTest(op, false, target); } } - Expr* last = sequence.back(); - EmitTest(last, jump_on_true, target); + EmitTest(sequence.back(), jump_on_true, target); __ bind(&fallthrough); } +#if 0 static inline OPCODE CmpTokenToOp(int token) { @@ -960,7 +1016,9 @@ CmpTokenToOp(int token) return OP_HALT; } } +#endif +#if 0 bool CodeGenerator::EmitChainedCompareExprTest(ChainedCompareExpr* root, bool jump_on_true, Label* target) @@ -1024,21 +1082,6 @@ CodeGenerator::EmitChainedCompareExpr(ChainedCompareExpr* root) } } -void -CodeGenerator::EmitTernaryExpr(TernaryExpr* expr) -{ - EmitExpr(expr->first()); - - Label flab1, flab2; - - __ emit(OP_JZER, &flab1); - EmitExpr(expr->second()); - __ emit(OP_JUMP, &flab2); - __ bind(&flab1); - EmitExpr(expr->third()); - __ bind(&flab2); -} - void CodeGenerator::EmitSymbolExpr(SymbolExpr* expr) { @@ -1062,17 +1105,23 @@ CodeGenerator::EmitSymbolExpr(SymbolExpr* expr) assert(false); } } +#endif -void -CodeGenerator::EmitIndexExpr(IndexExpr* expr) -{ - EmitExpr(expr->base()); +void CodeGenerator::EmitLoadIndexOp(ir::IndexOp* op) { + EmitIndexOp(op); - auto& base_val = expr->base()->val(); + if (op->base()->type()->isCharArray()) + __ emit(OP_LODB_I, 1); + else + __ emit(OP_LOAD_I); +} + +void CodeGenerator::EmitIndexOp(ir::IndexOp* op) { + EmitValue(op->base()); cell_t rank_size = sizeof(cell_t); - auto array_type = base_val.type()->as(); + auto array_type = op->base()->type()->as(); if (!array_type->inner()->isArray()) { if (array_type->inner()->isChar()) rank_size = 1; @@ -1082,13 +1131,12 @@ CodeGenerator::EmitIndexExpr(IndexExpr* expr) assert(rank_size == 1 || (rank_size % sizeof(cell_t) == 0)); - const auto& idxval = expr->index()->val(); - if (idxval.ident == iCONSTEXPR) { - if (idxval.constval() != 0) - __ emit(OP_ADD_C, idxval.constval() * rank_size); + if (auto cv = op->index()->as()) { + if (cv->value() != 0) + __ emit(OP_ADD_C, cv->value() * rank_size); } else { __ emit(OP_PUSH_PRI); - EmitExpr(expr->index()); + EmitValue(op->index()); if (array_type->size()) { __ emit(OP_BOUNDS, array_type->size() - 1); /* run time check for array bounds */ @@ -1107,36 +1155,18 @@ CodeGenerator::EmitIndexExpr(IndexExpr* expr) __ emit(OP_IDXADDR); } } - - // The indexed item is another array (multi-dimensional arrays). - if (array_type->inner()->isArray()) { - assert(expr->val().type()->isArray()); - __ emit(OP_LOAD_I); - } } -void -CodeGenerator::EmitFieldAccessExpr(FieldAccessExpr* expr) -{ - assert(expr->token() == '.'); - - // Note that we do not load an iACCESSOR here, we only make sure the base - // is computed. Emit() never performs loads on l-values, that ability is - // reserved for RvalueExpr(). - EmitExpr(expr->base()); - - // Only enum struct accesses have a resolved decl. - if (!expr->resolved()) - return; +void CodeGenerator::EmitFieldRef(ir::FieldRef* ref) { + EmitValue(ref->base()); - if (LayoutFieldDecl* field = expr->resolved()->as()) { - if (field->offset()) { - __ const_alt(field->offset() << 2); - __ emit(OP_ADD); - } + if (ref->field()->offset()) { + __ const_alt(ref->field()->offset() << 2); + __ emit(OP_ADD); } } +#if 0 void CodeGenerator::EmitCallExpr(CallExpr* call) { // If returning an array, push a hidden parameter. if (call->fun()->return_array()) { @@ -1278,31 +1308,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 +1376,15 @@ void CodeGenerator::EmitReturnArrayStmt(ReturnStmt* stmt) { __ emit(OP_POP_PRI); __ emit(OP_ADD_C, iv_size * sizeof(cell)); __ emit(OP_MOVS, info->zeroes * sizeof(cell)); +#endif } -void -CodeGenerator::EmitReturnStmt(ReturnStmt* stmt) -{ - if (stmt->expr()) { - EmitExpr(stmt->expr()); +void CodeGenerator::EmitReturn(ir::Return* node) { + if (node->val()) { + EmitValue(node->val()); - const auto& v = stmt->expr()->val(); - if (v.type()->isArray()) - EmitReturnArrayStmt(stmt); + if (node->val()->type()->isArray()) + EmitReturnArray(node); } else { /* this return statement contains no expression */ __ const_pri(0); @@ -1368,6 +1394,7 @@ CodeGenerator::EmitReturnStmt(ReturnStmt* stmt) __ emit(OP_RETN); } +#if 0 void CodeGenerator::EmitDeleteStmt(DeleteStmt* stmt) { @@ -1513,10 +1540,10 @@ void CodeGenerator::InvokeSetter(MethodmapPropertyDecl* prop, bool save_pri) { if (save_pri) __ emit(OP_POP_PRI); } +#endif -void -CodeGenerator::EmitDoWhileStmt(DoWhileStmt* stmt) -{ +void CodeGenerator::EmitDoWhile(ir::DoWhile* loop) { + auto stmt = loop->stmt(); int token = stmt->token(); assert(token == tDO || token == tWHILE); @@ -1525,37 +1552,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 +1596,14 @@ CodeGenerator::EmitDoWhileStmt(DoWhileStmt* stmt) } else { EmitTest(cond, false, &loop_cx.break_to); } - EmitStmt(body); - if (!body->IsTerminal()) - __ emit(OP_JUMP, &loop_cx.continue_to); + EmitBlock(body); + __ emit(OP_JUMP, &loop_cx.continue_to); } __ bind(&loop_cx.break_to); } -void -CodeGenerator::EmitLoopControl(int token) -{ +void CodeGenerator::EmitLoopControl(int token) { assert(loop_); assert(token == tBREAK || token == tCONTINUE); @@ -1600,6 +1622,7 @@ CodeGenerator::EmitLoopControl(int token) __ emit(OP_JUMP, &loop_->continue_to); } +#if 0 void CodeGenerator::EmitForStmt(ForStmt* stmt) { @@ -1685,11 +1708,10 @@ CodeGenerator::EmitForStmt(ForStmt* stmt) popstacklist(true); } } +#endif -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 +1720,23 @@ CodeGenerator::EmitSwitchStmt(SwitchStmt* stmt) // Note: we use map for ordering so the case table is sorted. std::map case_labels; - for (const auto& case_entry : stmt->cases()) { - Stmt* stmt = case_entry.second; - + for (const auto& [labels, block] : insn->cases()) { Label label; __ bind(&label); - for (const auto& expr : case_entry.first) { - const auto& v = expr->val(); - assert(v.ident == iCONSTEXPR); - - case_labels.emplace(v.constval(), label); - } + for (const auto& label_value : labels) + case_labels.emplace(label_value, label); - EmitStmt(stmt); - if (!stmt->IsTerminal()) - __ emit(OP_JUMP, &exit_label); + EmitBlock(block); + __ emit(OP_JUMP, &exit_label); } Label default_label; Label* defcase = &exit_label; - if (stmt->default_case()) { + if (insn->default_case()) { __ bind(&default_label); - EmitStmt(stmt->default_case()); - if (!stmt->default_case()->IsTerminal()) - __ emit(OP_JUMP, &exit_label); + EmitBlock(insn->default_case()); + __ emit(OP_JUMP, &exit_label); defcase = &default_label; } @@ -1736,62 +1750,83 @@ 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(); + EmitBlock(fun->body()); + popstacklist(false); } assert(!has_stack_or_heap_scopes()); + // :TODO: remove this! + __ emit(OP_CONST_PRI, 0); + __ emit(OP_RETN); + // If return keyword is missing, we added it in the semantic pass. __ emit(OP_ENDPROC); stack_scopes_.clear(); heap_scopes_.clear(); - info->cg()->pcode_end = asm_.pc(); - info->cg()->max_local_stack = max_func_memory_; + fun->set_pcode_end(asm_.pc()); + fun->set_max_local_stack(max_func_memory_); // In case there is no callgraph, we still need to track which function has // the biggest stack. max_script_memory_ = std::max(max_script_memory_, max_func_memory_); } -void -CodeGenerator::EmitBreak() -{ +void CodeGenerator::EmitBreak() { if (last_break_op_ && *last_break_op_ == asm_.position()) return; __ emit(OP_BREAK); last_break_op_.init(asm_.position()); } +#if 0 void CodeGenerator::EmitEnumStructDecl(EnumStructDecl* decl) { @@ -1811,23 +1846,24 @@ CodeGenerator::EmitMethodmapDecl(MethodmapDecl* decl) for (const auto& method : decl->methods()) EmitFunctionDecl(method); } +#endif -void CodeGenerator::EmitCall(FunctionDecl* fun, cell nargs) { +void CodeGenerator::EmitCall(ir::Function* fun, cell nargs) { assert(fun->is_live()); - if (fun->is_native()) { - if (!fun->cg()->label.bound()) { - __ bind_to(&fun->cg()->label, native_list_.size()); + if (fun->decl()->is_native()) { + if (!fun->label().bound()) { + __ bind_to(&fun->label(), native_list_.size()); native_list_.emplace_back(fun); } - __ sysreq_n(&fun->cg()->label, nargs); + __ sysreq_n(&fun->label(), nargs); } else { __ emit(OP_PUSH_C, nargs); - __ emit(OP_CALL, &fun->cg()->label); + __ emit(OP_CALL, &fun->label()); auto node = callgraph_.find(fun_); if (node == callgraph_.end()) - callgraph_.emplace(fun_, tr::vector{fun}); + callgraph_.emplace(fun_, tr::vector{fun}); else node->second.emplace_back(fun); } @@ -1835,6 +1871,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 +1976,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 +2057,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 +2094,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 +2138,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 +2153,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 +2166,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 +2178,7 @@ bool CodeGenerator::ComputeStackUsage(CallGraph::iterator caller_iter) { return false; } - max_script_memory_ = std::max(caller->cg()->max_local_stack + - caller->cg()->max_callee_stack, + max_script_memory_ = std::max(caller->max_local_stack() + caller->max_callee_stack(), max_script_memory_); return true; } diff --git a/compiler/code-generator.h b/compiler/code-generator.h index 479b4bca7..e6c329586 100644 --- a/compiler/code-generator.h +++ b/compiler/code-generator.h @@ -28,6 +28,7 @@ #include "stl/stl-unordered-map.h" #include "data-queue.h" #include "errors.h" +#include "ir.h" #include "parse-node.h" #include "smx-assembly-buffer.h" @@ -40,14 +41,15 @@ class ParseTree; class CodeGenerator final { public: - CodeGenerator(CompileContext& cc, ParseTree* tree); + CodeGenerator(CompileContext& cc, std::shared_ptr mod); bool Generate(); - void LinkPublicFunction(FunctionDecl* decl, uint32_t id); + void LinkPublicFunction(ir::Function* fun, uint32_t id); + std::shared_ptr mod() const { return mod_; } const tr::vector& debug_strings() const { return debug_strings_; } - const tr::vector& native_list() const { return native_list_; } + const tr::vector& native_list() const { return native_list_; } const uint8_t* code_ptr() const { return asm_.bytes(); } uint32_t code_size() const { return (uint32_t)asm_.size(); } @@ -58,13 +60,20 @@ 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 EmitDoWhileStmt(DoWhileStmt* stmt); @@ -73,36 +82,61 @@ class CodeGenerator final void EmitFunctionDecl(FunctionDecl* info); void EmitEnumStructDecl(EnumStructDecl* info); void EmitMethodmapDecl(MethodmapDecl* info); - void EmitReturnStmt(ReturnStmt* stmt); - void EmitReturnArrayStmt(ReturnStmt* stmt); + void EmitReturn(ir::Return* node); + void EmitReturnArray(ir::Return* node); // Expressions. void EmitExpr(Expr* expr); + void EmitValue(ir::Value* val); + void EmitLoad(ir::Load* load); + void EmitLoadVariable(ir::VariableRef* ref); + void EmitStore(ir::Store* op); + void EmitConst(ir::Const* cv); + void EmitCommaOp(ir::CommaOp* op); + void EmitCallOp(ir::CallOp* call); void EmitTest(Expr* expr, bool jump_on_true, sp::Label* target); - void EmitUnary(UnaryExpr* expr); - void EmitIncDec(IncDecExpr* expr); + void EmitTest(ir::Value* test, bool jump_on_true, sp::Label* target); + void EmitUnary(ir::UnaryOp* op); + void EmitBinary(ir::BinaryOp* op); + void EmitTernaryOp(ir::TernaryOp* op); + void EmitCharArrayLiteral(ir::CharArrayLiteral* val); + void 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 EmitIndexOp(ir::IndexOp* op); + void EmitLoadIndexOp(ir::IndexOp* op); void EmitFieldAccessExpr(FieldAccessExpr* expr); + void EmitFieldRef(ir::FieldRef* op); void EmitCallExpr(CallExpr* expr); void EmitDefaultArgExpr(DefaultArgExpr* expr); void EmitCallUserOpExpr(CallUserOpExpr* expr); void EmitNewArrayExpr(NewArrayExpr* expr); + // Calculate the address of an lvalue. + // + // A true return indicates the lvalue can only be computed once, for example + // the base or index are not idempotent, and the address is stored in PRI. A + // false value means nothing happened. + bool EmitPrecalcLvalue(ir::Lvalue* lval); + + // Load from an address calculated by EmitPrecalcLvalue. + void LoadPrecalcLvalue(ir::Lvalue* lval); + // Logical test helpers. bool EmitUnaryExprTest(UnaryExpr* expr, bool jump_on_true, sp::Label* target); + bool EmitUnaryExprTest(ir::UnaryOp* op, bool jump_on_true, sp::Label* target); void EmitLogicalExprTest(LogicalExpr* expr, bool jump_on_true, sp::Label* target); + void EmitLogicalExprTest(ir::BinaryOp* op, bool jump_on_true, sp::Label* target); bool EmitChainedCompareExprTest(ChainedCompareExpr* expr, bool jump_on_true, sp::Label* target); void EmitDefaultArray(Expr* expr, ArgDecl* arg); void EmitUserOp(const UserOperation& user_op, value* lval); - void EmitCall(FunctionDecl* fun, cell nargs); + void EmitCall(ir::Function* fun, cell nargs); void EmitInc(const value* lval); void EmitDec(const value* lval); void InvokeGetter(MethodmapPropertyDecl* method); @@ -177,7 +211,7 @@ class CodeGenerator final // Stack functions void pushstacklist(); void popstacklist(bool codegen); - int markstack(ParseNode* node, MemuseType type, int size); + cell_t markstack(ParseNode* node, MemuseType type, int size); void modheap_for_scope(const MemoryScope& scope); void modstk_for_scope(const MemoryScope& scope); @@ -196,7 +230,7 @@ class CodeGenerator final void AllocInScope(ParseNode* node, MemoryScope& scope, MemuseType type, int size); int PopScope(tr::vector& scope_list); - using CallGraph = tr::unordered_map>; + using CallGraph = tr::unordered_map>; bool ComputeStackUsage(); bool ComputeStackUsage(CallGraph::iterator caller_iter); @@ -218,11 +252,12 @@ class CodeGenerator final private: CompileContext& cc_; ParseTree* tree_; - FunctionDecl* fun_ = nullptr; + std::shared_ptr mod_; + ir::Function* fun_ = nullptr; int max_script_memory_ = 0; tr::vector debug_strings_; - tr::vector native_list_; + tr::vector native_list_; SmxAssemblyBuffer asm_; DataQueue data_; diff --git a/compiler/compile-context.h b/compiler/compile-context.h index c7784bd6c..2ca123cb6 100644 --- a/compiler/compile-context.h +++ b/compiler/compile-context.h @@ -22,6 +22,7 @@ #include #include +#include #include #include "array-data.h" @@ -74,15 +75,15 @@ class CompileContext final TypeManager* types() const { return types_.get(); } StringPool* atoms() { return &atoms_; } - Atom* atom(const std::string& str) { - return atoms_.add(str); - } Atom* atom(const char* str, size_t length) { return atoms_.add(str, length); } Atom* atom(const char* str) { return atoms_.add(str); } + Atom* atom(std::string_view sv) { + return atoms_.add(sv); + } const std::string& default_include() const { return default_include_; } void set_default_include(const std::string& file) { default_include_ = file; } diff --git a/compiler/expressions.cpp b/compiler/expressions.cpp index 9082bbe16..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..0cf49e624 --- /dev/null +++ b/compiler/ir.cpp @@ -0,0 +1,116 @@ +// vim: set ts=8 sts=4 sw=4 tw=99 et: +// +// Copyright (c) AlliedModders 2024 +// +// This software is provided "as-is", without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from +// the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software in +// a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +#include "ir.h" + +namespace sp { +namespace cc { +namespace ir { + +bool Value::HasSideEffects() { + switch (kind_) { + case IrKind::Store: + case IrKind::IncDecOp: + case IrKind::CallOp: + return true; + case IrKind::PropertyRef: { + auto ir = as(); + return ir->val()->HasSideEffects(); + } + case IrKind::TempRef: { + auto ir = as(); + return ir->val()->HasSideEffects(); + } + case IrKind::FieldRef: { + auto ir = as(); + return ir->base()->HasSideEffects(); + } + case IrKind::IndexOp: { + auto ir = as(); + return ir->base()->HasSideEffects() || ir->index()->HasSideEffects(); + } + case IrKind::Load: { + auto ir = as(); + return ir->lval()->HasSideEffects(); + } + case IrKind::TernaryOp: { + auto ir = as(); + return ir->select()->HasSideEffects() || + ir->on_true()->HasSideEffects() || + ir->on_false()->HasSideEffects(); + } + case IrKind::BinaryOp: { + auto ir = as(); + return ir->left()->HasSideEffects() || + ir->right()->HasSideEffects(); + } + case IrKind::CommaOp: { + auto ir = as(); + for (const auto& val : ir->values()) { + if (val->HasSideEffects()) + return true; + } + return false; + } + case IrKind::Array: { + auto ir = as(); + for (const auto& val : ir->values()) { + if (val->HasSideEffects()) + return true; + } + return false; + } + case IrKind::UnaryOp: { + auto ir = as(); + return ir->val()->HasSideEffects(); + } + default: + return false; + } +} + +void Function::AddReferenceTo(Function* other) { + if (!refers_to_) { + auto& cc = CompileContext::get(); + refers_to_ = cc.allocator().alloc>(); + } + for (Function* decl : *refers_to_) { + if (decl == other) + return; + } + refers_to_->emplace_front(other); +} + +bool Lvalue::AddressCalculationRequiresTemp() { + switch (kind()) { + case IrKind::VariableRef: + return false; + case IrKind::IndexOp: + case IrKind::FieldRef: + return true; + default: + assert(false); + return false; + } +} + +} // namespace ir +} // namespace cc +} // namespace sp diff --git a/compiler/ir.h b/compiler/ir.h new file mode 100644 index 000000000..2c09c7fde --- /dev/null +++ b/compiler/ir.h @@ -0,0 +1,851 @@ +// 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; + } 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) + {} + + static constexpr cell_t kInvalidAddr = std::numeric_limits::min(); + + cell_t addr() const { + assert(addr_ != kInvalidAddr); + return addr_; + } + void set_addr(cell_t addr) { + assert(addr_ == kInvalidAddr); + addr_ = addr; + } + + VarDeclBase* decl() const { return pn_u.var_decl; } + ir::Value* init() const { return init_; } + + static bool is_a(Node* node) { + return node->kind() == IrKind::Variable || node->kind() == IrKind::Argument; + } + + protected: + ir::Value* init_; + + private: + cell_t addr_ = kInvalidAddr; +}; + +class Argument final : public Variable { + public: + explicit Argument(ArgDecl* var) + : Variable(var, nullptr) + {} + + VarDeclBase* arg_decl() const { return pn_u.arg_decl; } + void set_init(ir::Value* init) { + init_ = init; + } + + 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); } + + 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; + 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, MethodmapMethodDecl* dtor) + : Insn(IrKind::Delete, stmt), + val_(val), + dtor_(dtor) + {} + + Value* val() const { return val_; } + MethodmapMethodDecl* dtor() const { return dtor_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Delete; } + + private: + Value* val_; + MethodmapMethodDecl* dtor_; +}; + +class ForLoop final : public Insn { + public: + ForLoop(ForStmt* stmt, InsnBlock* init, Value* cond, Value* advance, InsnBlock* body) + : Insn(IrKind::ForLoop, stmt), + init_(init), + cond_(cond), + advance_(advance), + body_(body) + {} + + InsnBlock* init() const { return init_; } + Value* cond() const { return cond_; } + Value* 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_; + Value* 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, FunctionDecl* target, Value* first = nullptr, + Value* second = nullptr, bool swapped = false) + : Value(IrKind::CallUserOp, expr, type), + target_(target), + first_(first), + second_(second), + swapped_(swapped) + {} + + FunctionDecl* target() const { return target_; } + Value* first() const { return first_; } + Value* second() const { return second_; } + bool swapped() const { return swapped_; } + + private: + FunctionDecl* target_; + Value* first_; + Value* second_; + bool swapped_; +}; + +class TypeRef final : public Value { + public: + TypeRef(Expr* expr, QualType type) + : Value(IrKind::TypeRef, expr, type) + {} + + static bool is_a(Node* op) { return op->kind() == IrKind::TypeRef; } +}; + +class FunctionRef final : public Value { + public: + FunctionRef(Expr* expr, QualType type, Function* fun) + : Value(IrKind::FunctionRef, expr, type), + fun_(fun) + {} + + Function* fun() const { return fun_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::FunctionRef; } + + private: + Function* fun_; +}; + +class Array final : public Value { + public: + Array(Expr* expr, QualType type, const std::vector& values) + : Value(IrKind::Array, expr, type) + { + new (&values_) decltype(values_)(values); + } + + const PoolArray& values() const { return values_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::Array; } + + private: + PoolArray values_; +}; + +class CommaOp final : public Value { + public: + CommaOp(Expr* expr, QualType type, const std::vector& values) + : Value(IrKind::CommaOp, expr, type) + { + new (&values_) decltype(values_)(values); + } + + const PoolArray& values() const { return values_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::CommaOp; } + + private: + PoolArray values_; +}; + +class CallOp final : public Value { + public: + CallOp(Expr* expr, QualType type, ir::Function* target, const std::vector& values) + : Value(IrKind::CallOp, expr, type), + target_(target) + { + new (&values_) decltype(values_)(values); + } + + ir::Function* target() const { return target_; } + const PoolArray& argv() const { return values_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::CallOp; } + + private: + ir::Function* target_; + PoolArray values_; +}; + +class BinaryOp final : public Value { + public: + BinaryOp(Expr* expr, QualType type, int token, Value* left, Value* right) + : Value(IrKind::BinaryOp, expr, type), + token_(token), + left_(left), + right_(right) + {} + + Value* left() const { return left_; } + Value* right() const { return right_; } + int token() const { return token_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::BinaryOp; } + + private: + int token_; + Value* left_; + Value* right_; +}; + +class TernaryOp final : public Value { + public: + TernaryOp(Expr* expr, QualType type, Value* select, Value* on_true, Value* on_false) + : Value(IrKind::TernaryOp, expr, type), + select_(select), + on_true_(on_true), + on_false_(on_false) + {} + + Value* select() const { return select_; } + Value* on_true() const { return on_true_; } + Value* on_false() const { return on_false_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::TernaryOp; } + + private: + Value* select_; + Value* on_true_; + Value* on_false_; +}; + +class ThisRef final : public Value { + public: + ThisRef(ThisExpr* expr, QualType type) + : Value(IrKind::ThisRef, expr, type) + {} +}; + +class 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 Lvalue : public Value { + public: + Lvalue(IrKind kind, Expr* expr, QualType type) + : Value(kind, expr, type) + {} + + static bool is_a(Node* op) { + return op->kind() == IrKind::VariableRef || + op->kind() == IrKind::IndexOp || + op->kind() == IrKind::TempRef || + op->kind() == IrKind::PropertyRef || + op->kind() == IrKind::FieldRef || + op->kind() == IrKind::TempAddr; + } + + bool AddressCalculationRequiresTemp(); +}; + +class Load final : public Value { + public: + Load(Expr* expr, QualType type, Lvalue* lval) + : Value(IrKind::Load, expr, type), + lval_(lval) + {} + + Lvalue* lval() const { return lval_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::Load; } + + private: + Lvalue* lval_; +}; + +class Store final : public Value { + public: + Store(Expr* expr, QualType type, Lvalue* lval, Value* val) + : Value(IrKind::Store, expr, type), + lval_(lval), + val_(val) + {} + + Lvalue* lval() const { return lval_; } + Value* val() const { return val_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::Store; } + + private: + Lvalue* lval_; + Value* val_; +}; + +// This is a special lvalue that is used in Store ops only. +// +// If double-evaluating an l-value could leak side effects, then it is wrapped +// in a TempAddr, which will evaluate the l-value and push its address onto the +// stack. A Load of this TempAddr is valid as long the stack depth hasn't +// changed. +class TempAddr final : public Lvalue { + public: + explicit TempAddr(Expr* expr, Lvalue* lval) + : Lvalue(IrKind::TempAddr, expr, lval->type()), + lval_(lval) + {} + + Lvalue* lval() const { return lval_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::TempAddr; } + + private: + Lvalue* lval_ = nullptr; +}; + +class VariableRef final : public Lvalue { + public: + VariableRef(Expr* expr, QualType type, Variable* var) + : Lvalue(IrKind::VariableRef, expr, type), + var_(var) + {} + + Variable* var() const { return var_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::VariableRef; } + + private: + Variable* var_; +}; + +class IndexOp final : public Lvalue { + public: + IndexOp(Expr* expr, QualType type, Value* base, Value* index) + : Lvalue(IrKind::IndexOp, expr, type), + base_(base), + index_(index) + {} + + Value* base() const { return base_; } + Value* index() const { return index_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::IndexOp; } + + private: + Value* base_; + Value* index_; +}; + +class FieldRef final : public Lvalue { + public: + FieldRef(Expr* expr, QualType type, Value* base, LayoutFieldDecl* field) + : Lvalue(IrKind::FieldRef, expr, type), + base_(base), + field_(field) + {} + + Value* base() const { return base_; } + LayoutFieldDecl* field() const { return field_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::FieldRef; } + + private: + Value* base_; + LayoutFieldDecl* field_; +}; + +class TempRef final : public Lvalue { + public: + TempRef(Expr* expr, Value* val) + : Lvalue(IrKind::TempRef, expr, val->type()), + val_(val) + {} + + Value* val() const { return val_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::TempRef; } + + private: + Value* val_; +}; + +class PropertyRef final : public Lvalue { + public: + PropertyRef(Expr* expr, QualType type, Value* val, MethodmapPropertyDecl* decl) + : Lvalue(IrKind::PropertyRef, expr, type), + val_(val), + decl_(decl) + {} + + Value* val() const { return val_; } + MethodmapPropertyDecl* decl() const { return decl_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::PropertyRef; } + + private: + Value* val_; + MethodmapPropertyDecl* decl_; +}; + +class GetAddressOp final : public Value { + public: + GetAddressOp(Expr* expr, QualType type, Lvalue* val) + : Value(IrKind::GetAddressOp, expr, type), + lval_(val) + {} + + Lvalue* lval() const { return lval_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::GetAddressOp; } + + private: + Lvalue* lval_; +}; + +} // namespace ir +} // namespace cc +} // namespace sp diff --git a/compiler/lexer.cpp b/compiler/lexer.cpp index 45bcddd73..95a8c306d 100644 --- a/compiler/lexer.cpp +++ b/compiler/lexer.cpp @@ -299,7 +299,7 @@ void Lexer::lex_float(full_token_t* tok, cell_t whole) { tok->id = tRATIONAL; } -int Lexer::preproc_expr(cell* val, Type** type) { +int Lexer::preproc_expr(cell* val, QualType* type) { ke::SaveAndSet forbid_const(&cc_.in_preprocessor(), true); return Parser::PreprocExpr(val, type); /* get value (or 0 on error) */ } @@ -331,7 +331,8 @@ void Lexer::HandleDirectives() { skiplevel_ = ifstack_.size(); cell val = 0; - preproc_expr(&val, NULL); /* get value (or 0 on error) */ + QualType ignore_type; + preproc_expr(&val, &ignore_type); /* get value (or 0 on error) */ CheckLineEmpty(); ifstack_.back() = (char)(val ? PARSEMODE : SKIPMODE); diff --git a/compiler/lexer.h b/compiler/lexer.h index 8a5e46d97..902ccfaac 100644 --- a/compiler/lexer.h +++ b/compiler/lexer.h @@ -397,7 +397,7 @@ class Lexer full_token_t* advance_token_ptr(); full_token_t* next_token(); void lexpop(); - int preproc_expr(cell* val, Type** type); + int preproc_expr(cell* val, QualType* type); void substallpatterns(unsigned char* line, int buffersize); bool substpattern(unsigned char* line, size_t buffersize, const char* pattern, const char* substitution, int& patternLen, int& substLen); diff --git a/compiler/main.cpp b/compiler/main.cpp index 2d0b7ec3c..a6b09bd48 100644 --- a/compiler/main.cpp +++ b/compiler/main.cpp @@ -135,6 +135,8 @@ int RunCompiler(int argc, char** argv, CompileContext& cc) { setcaption(); setconfig(argv[0]); /* the path to the include files */ + auto mod = std::make_shared(); + if (options->source_files.size() > 1) { report(452); goto cleanup; @@ -168,7 +170,7 @@ int RunCompiler(int argc, char** argv, CompileContext& cc) { cc.lexer()->Init(); { - Semantics sema(cc); + Semantics sema(cc, mod); Parser parser(cc, &sema); AutoCountErrors errors; @@ -192,9 +194,11 @@ int RunCompiler(int argc, char** argv, CompileContext& cc) { sema.set_context(nullptr); errors.Reset(); - if (!sema.Analyze(tree) || !errors.ok()) + if (!sema.Analyze(tree)) goto cleanup; + assert(errors.ok()); + tree->stmts()->ProcessUses(sc); ok = true; } @@ -211,7 +215,7 @@ int RunCompiler(int argc, char** argv, CompileContext& cc) { cc.set_shutting_down(); cc.reports()->DumpErrorReport(true); - CodeGenerator cg(cc, tree); + CodeGenerator cg(cc, mod); if (tree && compile_ok) compile_ok = cg.Generate(); diff --git a/compiler/messages.h b/compiler/messages.h index fd746b0cd..a691756fc 100644 --- a/compiler/messages.h +++ b/compiler/messages.h @@ -56,7 +56,7 @@ static const char* errmsg[] = { /*029*/ "invalid expression, assumed zero\n", /*030*/ "compound statement not closed at the end of file (started at line %d)\n", /*031*/ "unknown directive\n", - /*032*/ "array index out of bounds (variable \"%s\")\n", + /*032*/ "array index out of bounds\n", /*033*/ "array must be indexed (variable \"%s\")\n", /*034*/ "argument does not have a default value (argument %d)\n", /*035*/ "argument type mismatch (argument %d)\n", @@ -214,7 +214,7 @@ static const char* errmsg[] = { /*176*/ "non-static method or property '%s' must be called with a value of type '%s'\n", /*177*/ "static method '%s' must be invoked via its type (try '%s.%s')\n", /*178*/ "cannot coerce %s[] to %s[]; storage classes differ\n", - /*179*/ "cannot assign %s[] to %s[], storage classes differ\n", + /*179*/ "unused179\n", /*180*/ "function return type differs from prototype. expected '%s', but got '%s'\n", /*181*/ "function argument named '%s' differs from prototype\n", /*182*/ "functions that return arrays cannot be used as callbacks\n", @@ -336,4 +336,7 @@ static const char* errmsg_ex[] = { /*450*/ "no viable conversion from \"%s\" to \"%s\"\n", /*451*/ "function %s returns an array but return type is not marked as an array\n", /*452*/ "multiple command-line source files are no longer supported\n", + /*453*/ "operator \"%s\" not defined for type \"%s\"\n", + /*454*/ "type \"%s\" is not a scalar type\n", + /*455*/ "illegal assignment (not an l-value)\n", }; diff --git a/compiler/name-resolution.cpp b/compiler/name-resolution.cpp index 2e3adf638..176345606 100644 --- a/compiler/name-resolution.cpp +++ b/compiler/name-resolution.cpp @@ -202,9 +202,9 @@ bool EnumDecl::EnterNames(SemaContext& sc) { AutoErrorPos error_pos(field->pos()); if (field->value() && field->value()->Bind(sc) && sc.sema()->CheckExpr(field->value())) { - Type* field_type = nullptr; - if (field->value()->EvalConst(&value, &field_type)) - matchtag(type_, field_type, MATCHTAG_COERCE | MATCHTAG_ENUM_ASSN); + QualType field_type; + if (Expr::EvalConst(field->value(), &value, &field_type)) + matchtag(type_, *field_type, MATCHTAG_COERCE | MATCHTAG_ENUM_ASSN); else error(field->pos(), 80); } @@ -368,9 +368,7 @@ bool ConstDecl::EnterNames(SemaContext& sc) { return true; } -bool -ConstDecl::Bind(SemaContext& sc) -{ +bool ConstDecl::Bind(SemaContext& sc) { if (sc.func() && !EnterNames(sc)) return false; @@ -379,17 +377,20 @@ ConstDecl::Bind(SemaContext& sc) if (!expr_->Bind(sc)) return false; - if (!sc.sema()->CheckExpr(expr_)) + auto val = sc.sema()->CheckExpr(expr_); + if (!val) return false; - Type* type; - if (!expr_->EvalConst(&value_, &type)) { + auto cv = val->as(); + if (!cv) { report(expr_, 8); return false; } AutoErrorPos aep(pos_); - matchtag(type_.type, type, 0); + matchtag(type_.type, *cv->type(), 0); + + value_ = cv->value(); return true; } @@ -808,17 +809,13 @@ FunctionDecl::BindArgs(SemaContext& sc) { AutoCountErrors errors; - size_t arg_index = 0; for (auto& var : args_) { const auto& typeinfo = var->type_info(); AutoErrorPos pos(var->pos()); - if (typeinfo.is_varargs) { - /* redimension the argument list, add the entry iVARARGS */ - var->BindAddress(static_cast((arg_index + 3) * sizeof(cell))); + if (typeinfo.is_varargs) break; - } Type* type = typeinfo.type; if (type->isEnumStruct()) { @@ -826,19 +823,12 @@ FunctionDecl::BindArgs(SemaContext& sc) report(var->pos(), 135) << type; } - /* Stack layout: - * base + 0*sizeof(cell) == previous "base" - * base + 1*sizeof(cell) == function return address - * base + 2*sizeof(cell) == number of arguments - * base + 3*sizeof(cell) == first argument of the function - * So the offset of each argument is "(argcnt+3) * sizeof(cell)". - * - * Since arglist has an empty terminator at the end, we actually add 2. - */ - var->BindAddress(static_cast((arg_index + 3) * sizeof(cell))); - arg_index++; - - if (type->isArray() || typeinfo.type->isEnumStruct()) { + if (var->init()) + var->init()->Bind(sc); + +#if 0 + if (type->isArray() || type->isEnumStruct()) { + assert(false); if (sc.sema()->CheckVarDecl(var) && var->init_rhs()) fill_arg_defvalue(sc.cc(), var); } else { @@ -850,25 +840,26 @@ FunctionDecl::BindArgs(SemaContext& sc) var->set_default_value(new DefaultArg()); cell val; - Type* type; - if (!init->EvalConst(&val, &type)) { + QualType type; + if (!Expr::EvalConst(init, &val, &type)) { error(var->pos(), 8); // Populate to avoid errors. val = 0; - type = typeinfo.type; + type = QualType(typeinfo.type); } - var->default_value()->type = type; + var->default_value()->type = *type; var->default_value()->val = ke::Some(val); - matchtag(var->type(), type, MATCHTAG_COERCE); + matchtag(var->type(), *type, MATCHTAG_COERCE); } } - if (var->type()->isReference()) + if (type->isReference()) var->set_is_read(); if (is_callback_ || is_public_) var->set_is_read(); +#endif /* arguments of a public function may not have a default value */ if (is_public_ && var->default_value()) @@ -896,6 +887,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..93ea2fb39 100644 --- a/compiler/parse-node.h +++ b/compiler/parse-node.h @@ -588,26 +588,13 @@ class Expr : public ParseNode can_alloc_heap_(false) {} - // Flatten a series of binary expressions into a single list. - virtual void FlattenLogical(int token, std::vector* out); - - // Fold the expression into a constant. The expression must have been - // bound and analyzed. False indicates the expression is non-constant. - // - // If an expression folds constants during analysis, it can return false - // here. ExprToConst handles both cases. - virtual bool FoldToConstant() { - return false; - } - // Process any child nodes whose value is consumed. virtual void ProcessUses(SemaContext& sc) = 0; // Process any child nodes whose value is not consumed. virtual void ProcessDiscardUses(SemaContext& sc) { ProcessUses(sc); } - // Evaluate as a constant. Returns false if non-const. This is a wrapper - // around FoldToConstant(). - bool EvalConst(cell* value, Type** type); + // Evaluate as a constant. Returns false if non-const. + static bool EvalConst(Expr* expr, cell* value, QualType* type); // Return whether or not the expression is idempotent (eg has side effects). bool HasSideEffects(); @@ -700,7 +687,7 @@ class BinaryExpr final : public BinaryExprBase public: BinaryExpr(const token_pos_t& pos, int token, Expr* left, Expr* right); - bool FoldToConstant() override; + bool ConstantFold(cell* value, QualType* type); static bool is_a(Expr* node) { return node->kind() == ExprKind::BinaryExpr; } @@ -731,8 +718,6 @@ class LogicalExpr final : public BinaryExprBase : BinaryExprBase(ExprKind::LogicalExpr, pos, token, left, right) {} - void FlattenLogical(int token, std::vector* out) override; - static bool is_a(Expr* node) { return node->kind() == ExprKind::LogicalExpr; } }; @@ -787,7 +772,6 @@ class TernaryExpr final : public Expr ok &= third_->Bind(sc); return ok; } - bool FoldToConstant() override; void ProcessUses(SemaContext& sc) override; void ProcessDiscardUses(SemaContext& sc) override; @@ -927,17 +911,21 @@ class NamedArgExpr : public Expr public: NamedArgExpr(const token_pos_t& pos, Atom* name, Expr* expr) : Expr(ExprKind::NamedArgExpr, pos), - name(name), - expr(expr) + name_(name), + expr_(expr) {} - bool Bind(SemaContext& sc) override { return expr->Bind(sc); } - void ProcessUses(SemaContext& sc) override { expr->ProcessUses(sc); } + bool Bind(SemaContext& sc) override { return expr_->Bind(sc); } + void ProcessUses(SemaContext& sc) override { expr_->ProcessUses(sc); } static bool is_a(Expr* node) { return node->kind() == ExprKind::NamedArgExpr; } - Atom* name; - Expr* expr; + Atom* name() const { return name_; } + Expr* expr() const { return expr_; } + + private: + Atom* name_; + Expr* expr_; }; class CallExpr final : public Expr diff --git a/compiler/parser.cpp b/compiler/parser.cpp index c6f004351..cadbcd87b 100644 --- a/compiler/parser.cpp +++ b/compiler/parser.cpp @@ -299,10 +299,11 @@ Parser::parse_unknown_decl(const full_token_t* tok) return nullptr; } -bool Parser::PreprocExpr(cell* val, Type** type) { +bool Parser::PreprocExpr(cell* val, QualType* type) { auto& cc = CompileContext::get(); - Semantics sema(cc); + // :TODO: fix this so we don't use Sema. + Semantics sema(cc, nullptr); Parser parser(cc, &sema); auto expr = parser.hier14(); if (!expr) @@ -315,7 +316,8 @@ bool Parser::PreprocExpr(cell* val, Type** type) { if (!expr->Bind(sc) || !sema.CheckExpr(expr)) return false; - return expr->EvalConst(val, type); + + return Expr::EvalConst(expr, val, type); } Stmt* diff --git a/compiler/parser.h b/compiler/parser.h index c2896ba76..f13a901c1 100644 --- a/compiler/parser.h +++ b/compiler/parser.h @@ -38,7 +38,7 @@ class Parser Parser(CompileContext& cc, Semantics* sema); ~Parser(); - static bool PreprocExpr(cell* val, Type** type); + static bool PreprocExpr(cell* val, QualType* type); ParseTree* Parse(); diff --git a/compiler/pool-objects.h b/compiler/pool-objects.h index 41b48ab17..a9d724ecb 100644 --- a/compiler/pool-objects.h +++ b/compiler/pool-objects.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include "compile-context.h" @@ -137,13 +138,13 @@ template using PoolForwardList = std::forward_list>; struct KeywordTablePolicy { - static bool matches(const sp::CharsAndLength& a, const sp::CharsAndLength& b) { - if (a.length() != b.length()) + static bool matches(const std::string_view& a, const std::string_view& b) { + if (a.size() != b.size()) return false; - return strncmp(a.str(), b.str(), a.length()) == 0; + return strncmp(a.data(), b.data(), a.size()) == 0; } - static uint32_t hash(const sp::CharsAndLength& key) { - return ke::HashCharSequence(key.str(), key.length()); + static uint32_t hash(const std::string_view& key) { + return ke::HashCharSequence(key.data(), key.size()); } }; diff --git a/compiler/qualtype.h b/compiler/qualtype.h index e12f1228c..573e3c546 100644 --- a/compiler/qualtype.h +++ b/compiler/qualtype.h @@ -30,12 +30,18 @@ class Type; // Compact encoding of type + constness. class QualType { public: + QualType() : impl_(nullptr) {} explicit QualType(Type* type) { impl_ = type; } - explicit QualType(Type* type, bool is_const) { + QualType(Type* type, bool is_const) { impl_ = ke::SetPointerBits(type, is_const ? 1 : 0); } + QualType(QualType type, bool is_const) + : impl_(type.impl_) + { + impl_ = ke::SetPointerBits(impl_, is_const ? 1 : 0); + } bool is_const() const { return ke::GetPointerBits<2>(impl_) == 1; @@ -50,6 +56,7 @@ class QualType { uint32_t hash() const { return ke::HashPointer(impl_); } + explicit operator bool() const { return impl_ != nullptr; } bool operator ==(const QualType& other) const { return impl_ == other.impl_; } bool operator !=(const QualType& other) const { return impl_ != other.impl_; } diff --git a/compiler/semantics.cpp b/compiler/semantics.cpp index c9a83e1eb..7f98de0cd 100644 --- a/compiler/semantics.cpp +++ b/compiler/semantics.cpp @@ -22,12 +22,14 @@ #include "semantics.h" #include +#include #include #include "array-helpers.h" #include "code-generator.h" #include "errors.h" #include "expressions.h" +#include "ir.h" #include "lexer.h" #include "parse-node.h" #include "sctracker.h" @@ -38,11 +40,11 @@ namespace sp { namespace cc { -Semantics::Semantics(CompileContext& cc) - : cc_(cc) -{ - types_ = cc.types(); -} +Semantics::Semantics(CompileContext& cc, std::shared_ptr mod) + : cc_(cc), + types_(cc.types()), + mod_(std::move(mod)) +{} bool Semantics::Analyze(ParseTree* tree) { SemaContext sc(this); @@ -55,6 +57,11 @@ bool Semantics::Analyze(ParseTree* tree) { DeduceLiveness(); DeduceMaybeUsed(); + // Remove functions that are not live. + ke::EraseIf(&mod_->functions(), [](ir::Function* fun) -> bool { + return !fun->is_live(); + }); + // This inserts missing return statements at the global scope, so it cannot // be omitted. bool has_public = false; @@ -67,6 +74,8 @@ bool Semantics::Analyze(ParseTree* tree) { return false; } + assert(errors.ok()); + // All heap allocations must be owned by a ParseNode. assert(!pending_heap_allocation_); return true; @@ -173,34 +182,39 @@ bool Semantics::CheckVarDecl(VarDeclBase* decl) { if (!decl->as() && is_const && !decl->init() && !decl->is_public()) report(decl->pos(), 251); - // CheckArrayDecl works on enum structs too. + ir::Value* init = nullptr; if (type->isArray() || type->isEnumStruct()) { - if (!CheckArrayDeclaration(decl)) + if (!CheckArrayDeclaration(decl, &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()) { + 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); + } } } + auto def = new ir::Variable(decl, init); + if (fun_) { + assert(sc_->local_vars().find(decl) == sc_->local_vars().end()); + ir_->add(def); + sc_->local_vars().emplace(decl, def); + } else { + assert(global_vars_.find(decl) == global_vars_.end()); + mod_->globals().emplace_back(def); + global_vars_.emplace(decl, def); + } + return true; } @@ -328,13 +342,13 @@ static inline int GetOperToken(int token) { } } -bool Semantics::CheckExpr(Expr* expr) { +ir::Value* Semantics::CheckExpr(Expr* expr, bool used) { 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(), used); case ExprKind::BinaryExpr: return CheckBinaryExpr(expr->to()); case ExprKind::LogicalExpr: @@ -369,26 +383,15 @@ bool Semantics::CheckExpr(Expr* expr) { return CheckTaggedValueExpr(expr->to()); case ExprKind::SizeofExpr: return CheckSizeofExpr(expr->to()); - case ExprKind::RvalueExpr: - return CheckWrappedExpr(expr, expr->to()->expr()); case ExprKind::NamedArgExpr: - return CheckWrappedExpr(expr, expr->to()->expr); + return CheckExpr(expr->to()->expr()); default: assert(false); report(expr, 420) << (int)expr->kind(); - return false; + return nullptr; } } -bool Semantics::CheckWrappedExpr(Expr* outer, Expr* inner) { - if (!CheckExpr(inner)) - return false; - - outer->val() = inner->val(); - outer->set_lvalue(inner->lvalue()); - return true; -} - CompareOp::CompareOp(const token_pos_t& pos, int token, Expr* expr) : pos(pos), token(token), @@ -397,20 +400,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 +482,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 +537,120 @@ RvalueExpr::RvalueExpr(Expr* expr) } } -void -RvalueExpr::ProcessUses(SemaContext& sc) -{ +void RvalueExpr::ProcessUses(SemaContext& sc) { expr_->MarkAndProcessUses(sc); } -bool Semantics::CheckUnaryExpr(UnaryExpr* unary) { +ir::Value* Semantics::CheckUnaryExpr(UnaryExpr* unary) { AutoErrorPos aep(unary->pos()); - auto expr = unary->expr(); - if (!CheckRvalue(expr)) - return false; - if (!CheckScalarType(expr)) - return false; - - if (expr->lvalue()) - expr = unary->set_expr(new RvalueExpr(expr)); - - auto& out_val = unary->val(); - out_val = expr->val(); + ir::Value* val = CheckRvalue(unary->expr()); + if (!val) + return nullptr; + if (!CheckScalarType(unary, val->type())) + return nullptr; // :TODO: check for invalid types UserOperation userop; switch (unary->token()) { - case '~': - if (out_val.ident == iCONSTEXPR) - out_val.set_constval(~out_val.constval()); - break; - case '!': - if (find_userop(*sc_, '!', out_val.type(), 0, 1, &out_val, &userop)) { - expr = unary->set_expr(new CallUserOpExpr(userop, expr)); - out_val = expr->val(); - unary->set_userop(); - } else if (out_val.ident == iCONSTEXPR) { - out_val.set_constval(!out_val.constval()); + case '~': { + if (val->type()->isFloat()) { + report(unary, 453) << "~" << val->type(); + return nullptr; } - out_val.set_type(types_->type_bool()); - break; - case '-': - if (out_val.ident == iCONSTEXPR && out_val.type()->isFloat()) { - float f = sp::FloatCellUnion(out_val.constval()).f32; - out_val.set_constval(sp::FloatCellUnion(-f).cell); - } else if (find_userop(*sc_, '-', out_val.type(), 0, 1, &out_val, &userop)) { - expr = unary->set_expr(new CallUserOpExpr(userop, expr)); - out_val = expr->val(); - unary->set_userop(); - } else if (out_val.ident == iCONSTEXPR) { - /* the negation of a fixed point number is just an integer negation */ - out_val.set_constval(-out_val.constval()); + + if (auto cv = val->as()) + return new ir::Const(unary, cv->type(), ~cv->value()); + + return new ir::UnaryOp(unary, val->type(), val); + } + case '!': { + auto type = types_->get_bool(); + + if (auto op = MaybeCallUserOp(unary, '!', val, nullptr)) + val = op; + + if (auto cv = val->as()) + return new ir::Const(unary, type, !cv->value()); + + return new ir::UnaryOp(unary, types_->get_bool(), val); + } + case '-': { + if (auto op = MaybeCallUserOp(unary, '~', val, nullptr)) + val = op; + + if (auto cv = val->as()) { + cell_t new_value; + if (cv->type()->isFloat()) { + float f = sp::FloatCellUnion(cv->value()).f32; + new_value = sp::FloatCellUnion(-f).cell; + } else { + new_value = -cv->value(); + } + return new ir::Const(unary, val->type(), new_value); } - break; + return new ir::UnaryOp(unary, val->type(), val); + } default: assert(false); + return nullptr; } - - if (out_val.ident != iCONSTEXPR) - out_val.ident = iEXPRESSION; - return true; } -void -UnaryExpr::ProcessUses(SemaContext& sc) -{ +void UnaryExpr::ProcessUses(SemaContext& sc) { expr_->MarkAndProcessUses(sc); } -bool Semantics::CheckIncDecExpr(IncDecExpr* incdec) { +ir::Value* Semantics::CheckIncDecExpr(IncDecExpr* incdec, bool used) { AutoErrorPos aep(incdec->pos()); - auto expr = incdec->expr(); - if (!CheckExpr(expr)) - return false; - if (!CheckScalarType(expr)) - return false; - if (!expr->lvalue()) { + auto val = CheckExpr(incdec->expr()); + if (!val) + return nullptr; + if (!CheckScalarType(incdec, val->type())) + return nullptr; + auto lval = val->as(); + if (!lval) { report(incdec, 22); - return false; + return nullptr; } - const auto& expr_val = expr->val(); - if (expr_val.ident != iACCESSOR) { - if (expr_val.sym && expr_val.sym->is_const()) { - report(incdec, 22); /* assignment to const argument */ - return false; - } - } else { - if (!expr_val.accessor()->setter()) { - report(incdec, 152) << expr_val.accessor()->name(); - return false; + if (lval->type().is_const()) { + report(incdec, 22); + return nullptr; + } + +#if 0 + if (auto prop = lval->as()) { + auto decl = prop->decl(); + if (!decl->setter()) { + report(incdec, 152) << decl->name(); + return nullptr; } - if (!expr_val.accessor()->getter()) { - report(incdec, 149) << expr_val.accessor()->name(); - return false; + if (!decl->getter()) { + report(incdec, 149) << decl->name(); + return nullptr; } - markusage(expr_val.accessor()->getter(), uREAD); - markusage(expr_val.accessor()->setter(), uREAD); + markusage(decl->getter(), uREAD); + markusage(decl->setter(), uREAD); } +#endif - Type* type = expr_val.type(); - if (type->isReference()) - type = type->inner(); - - find_userop(*sc_, incdec->token(), type, 0, 1, &expr_val, &incdec->userop()); +#if 0 + if (auto userop = MaybeCallUserOp(incdec, incdec->token(), , nullptr)) + assert(false); +#endif - // :TODO: more type checks - auto& val = incdec->val(); - val.ident = iEXPRESSION; - val.set_type(type); - return true; + auto right = new ir::Const(incdec, types_->get_int(), 1); + if (incdec->prefix() || !used) { + auto left = BuildRvalue(incdec, lval); + auto add = new ir::BinaryOp(incdec, left->type(), '+', left, right); + return new ir::Store(incdec, left->type(), lval, add); + } else { + assert(false); + return nullptr; + } } void @@ -705,180 +676,143 @@ 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); - if (IsAssignOp(token)) { - // Mark the left-hand side as written as soon as we can. - if (Decl* sym = left->val().sym) { - markusage(sym, uWRITTEN); + auto left = CheckExpr(expr->left()); + if (!left) + return nullptr; + auto right = CheckRvalue(expr->right()); + if (!right) + return nullptr; - // If it's an outparam, also mark it as read. - if (sym->vclass() == sARGUMENT && - (sym->type()->isReference() || sym->type()->isArray())) - { - markusage(sym, uREAD); - } - } else if (auto* accessor = left->val().accessor()) { - if (!accessor->setter()) { - report(expr, 152) << accessor->name(); - return false; - } - markusage(accessor->setter(), uREAD); - if (accessor->getter() && token != '=') - markusage(accessor->getter(), uREAD); + auto lval = left->as(); + if (IsAssignOp(token)) { + if (!lval) { + report(expr, 455); + return nullptr; } + if (!CheckAssignmentLHS(expr, lval)) + return nullptr; - if (!CheckAssignmentLHS(expr)) - return false; - if (token != '=' && !CheckRvalue(left->pos(), left->val())) - return false; - } else if (left->lvalue()) { - if (!CheckRvalue(left->pos(), left->val())) - return false; - left = expr->set_left(new RvalueExpr(left)); - } - - // RHS is always loaded. Note we do this after validating the left-hand side, - // so ValidateAssignment has an original view of RHS. - if (right->lvalue()) - right = expr->set_right(new RvalueExpr(right)); - - const auto& left_val = left->val(); - const auto& right_val = right->val(); + MarkUsage(lval, uWRITTEN); + if (token != '=') + MarkUsage(lval, uREAD); - auto oper_tok = expr->oper(); - if (oper_tok) { - assert(token != '='); + if (lval->AddressCalculationRequiresTemp()) + lval = new ir::TempAddr(expr->left(), lval); - 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 != '=') + left = BuildRvalue(expr->left(), lval); + } else if (lval) { + left = BuildRvalue(expr->left(), lval); } - // The assignment operator is overloaded separately. - if (IsAssignOp(token)) { - if (!CheckAssignmentRHS(expr)) - return false; + if (token != '=') { + if (!CheckScalarType(expr->left(), left->type())) + return nullptr; + if (!CheckScalarType(expr->right(), right->type())) + return nullptr; } - auto& val = expr->val(); - val.ident = iEXPRESSION; - val.set_type(left_val.type()); + ir::Value* out = nullptr; + if (token != '=') + out = MaybeCallUserOp(expr, oper_token, left, right); - auto& assignop = expr->assignop(); - if (assignop.sym) - val.set_type(assignop.sym->type()); + if (!out) { + TypeChecker::DoCoerce(expr->pos(), *left->type(), *right->type(), TypeChecker::Commutative); - if (oper_tok) { - auto& userop = expr->userop(); - if (find_userop(*sc_, oper_tok, left_val.type(), right_val.type(), 2, nullptr, &userop)) { - val.set_type(userop.sym->type()); - } else if (left_val.ident == iCONSTEXPR && right_val.ident == iCONSTEXPR) { - char boolresult = FALSE; - TypeChecker::DoCoerce(expr->pos(), left_val.type(), right_val.type()); - val.ident = iCONSTEXPR; - val.set_constval(calc(left_val.constval(), oper_tok, right_val.constval(), - &boolresult)); - } else { - // For the purposes of tag matching, we consider the order to be irrelevant. - Type* left_type = left_val.type(); - if (left_type->isReference()) - left_type = left_type->inner(); - - Type* right_type = right_val.type(); - if (right_type->isReference()) - right_type = right_type->inner(); - - TypeChecker::DoCoerce(expr->pos(), left_type, right_type, TypeChecker::Commutative); + auto left_cv = left->as(); + auto right_cv = right->as(); + if (left_cv && CanConstFoldType(left_cv->type()) && + right_cv && CanConstFoldType(right_cv->type())) + { + char is_bool = false; + cell result = calc(left_cv->value(), 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); } + } - if (IsChainedOp(token) || token == tlEQ || token == tlNE) - val.set_type(types_->type_bool()); + 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); } - return true; + if (IsAssignOp(token)) { + auto type = BuildRvalueType(lval->type()); + out = new ir::Store(expr, type, lval, out); + } + + return out; } -bool Semantics::CheckAssignmentLHS(BinaryExpr* expr) { - auto left = expr->left(); - int left_ident = left->val().ident; - if (left_ident == iARRAYCHAR) { +bool Semantics::CheckAssignmentLHS(BinaryExpr* expr, ir::Lvalue* lval) { + if (lval->type()->isCharArray()) { // This is a special case, assigned to a packed character in a cell // is permitted. return true; } - int oper_tok = expr->oper(); - if (auto left_array = left->val().type()->as()) { - // array assignment is permitted too (with restrictions) - if (oper_tok) { - report(expr, 23); - return false; - } - + // :TODO: is this needed? TypeChecker should cover it. + if (auto left_array = lval->type()->as()) { for (auto iter = left_array; iter; iter = iter->inner()->as()) { if (!iter->size()) { - report(left, 46); + report(expr->left(), 46); return false; } } return true; } - if (!left->lvalue()) { - report(expr, 22); - return false; - } - - const auto& left_val = left->val(); // may not change "constant" parameters - if (!expr->initializer() && left_val.sym && left_val.sym->is_const()) { + if (lval->type().is_const()) { report(expr, 22); return false; } return true; } -bool Semantics::CheckAssignmentRHS(BinaryExpr* expr) { - auto left = expr->left(); - auto right = expr->right(); - const auto& left_val = left->val(); - const auto& right_val = right->val(); +static inline void CheckSelfAssignment(BinaryExpr* expr, ir::Lvalue* lval, ir::Value* rval) { + auto left_var = lval->as(); + if (!left_var) + return; - if (left_val.ident == iVARIABLE) { - const auto& right_val = right->val(); - if (right_val.ident == iVARIABLE && right_val.sym == left_val.sym && !expr->oper()) - report(expr, 226) << left_val.sym->name(); // self-assignment - } + auto load = rval->as(); + if (!load) + return; - if (auto left_array = left_val.type()->as()) { - TypeChecker tc(expr, left_val.type(), right_val.type(), TypeChecker::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 = 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 +825,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 +854,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 +866,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 +923,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 +957,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 +1025,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 +1073,21 @@ TernaryExpr::ProcessDiscardUses(SemaContext& sc) third_->ProcessUses(sc); } -bool Semantics::CheckCastExpr(CastExpr* expr) { +ir::Value* Semantics::CheckCastExpr(CastExpr* expr) { AutoErrorPos aep(expr->pos()); - Type* atype = expr->type(); - if (atype->isVoid()) { + QualType to_type = QualType(expr->type()); + if (to_type->isVoid()) { report(expr, 144); - return false; + return nullptr; } - if (!CheckExpr(expr->expr())) - return false; - - auto& out_val = expr->val(); - - out_val = expr->expr()->val(); - expr->set_lvalue(expr->expr()->lvalue()); - - Type* ltype = out_val.type(); + ir::Value* val = CheckExpr(expr->expr()); + if (!val) + return nullptr; - auto actual_array = ltype->as(); + QualType from_type = val->type(); + auto actual_array = from_type->as(); if (actual_array) { // Unwind back to the inner. auto iter = actual_array; @@ -1234,32 +1096,39 @@ bool Semantics::CheckCastExpr(CastExpr* expr) { break; iter = iter->inner()->to(); } - ltype = iter->inner(); + from_type = QualType(iter->inner()); } - if (ltype->isObject() || atype->isObject()) { - TypeChecker::DoCoerce(expr->pos(), atype, out_val.type()); - } else if (ltype->isFunction() != atype->isFunction()) { + assert(!val->as()); + + if (from_type->isObject() || to_type->isObject()) { + //TypeChecker::DoCoerce(expr->pos(), to_type, from_type); + assert(false); + } else if (from_type->isFunction() != to_type->isFunction()) { // Warn: unsupported cast. report(expr, 237); - } else if (ltype->isFunction() && atype->isFunction()) { - TypeChecker::DoCoerce(expr->pos(), atype, out_val.type()); - } else if (out_val.type()->isVoid()) { + } else if (from_type->isFunction() && to_type->isFunction()) { + //TypeChecker::DoCoerce(expr->pos(), to_type, from_type); + assert(false); + } else if (from_type->isVoid()) { report(expr, 89); - } else if (atype->isEnumStruct() || ltype->isEnumStruct()) { - report(expr, 95) << atype; + return nullptr; + } else if (to_type->isEnumStruct() || from_type->isEnumStruct()) { + report(expr, 95) << to_type; + return nullptr; } - if (ltype->isReference() && !atype->isReference()) { - if (atype->isEnumStruct()) { + if (from_type->isReference() && !to_type->isReference()) { + if (to_type->isEnumStruct()) { report(expr, 136); - return false; + return nullptr; } - atype = types_->defineReference(atype); + to_type = QualType(types_->defineReference(*to_type)); } - if (actual_array) - atype = types_->redefineArray(atype, actual_array); - out_val.set_type(atype); - return true; + assert(!actual_array); + //if (actual_array) + // to_type = types_->redefineArray(to_type, actual_array); + + return val; } void @@ -1276,48 +1145,43 @@ void SymbolExpr::MarkUsed(SemaContext& sc) { // checks, so for now, we forbid it by default. Since the '.' operator *is* // prepared for this, we have a special analysis option to allow returning // types as values. -bool Semantics::CheckSymbolExpr(SymbolExpr* expr, bool allow_types) { +ir::Value* Semantics::CheckSymbolExpr(SymbolExpr* expr, bool allow_types) { AutoErrorPos aep(expr->pos()); auto decl = expr->decl(); if (!decl) { // This can happen if CheckSymbolExpr is called during name resolution. assert(cc_.reports()->total_errors() > 0); - return false; + return nullptr; } - auto& val = expr->val(); - val.ident = decl->ident(); - val.sym = decl; - // Don't expose the tag of old enumroots. Type* type = decl->type(); if (decl->as() && !type->asEnumStruct() && decl->ident() == iCONSTEXPR) { report(expr, 174) << decl->name(); - return false; + return nullptr; } - val.set_type(type); if (auto fun = decl->as()) { fun = fun->canonical(); if (fun->is_native()) { report(expr, 76); - return false; + return nullptr; } if (fun->return_array()) { report(expr, 182); - return false; + return nullptr; } if (!fun->impl()) { report(expr, 4) << fun->name(); - return false; + return nullptr; } funcenum_t* fe = funcenum_for_symbol(cc_, fun); - // New-style "closure". - val.ident = iEXPRESSION; - val.set_type(fe->type); + // New-style "closure". TODO: when we get rid of funcenum_t, this won't + // be necessary. + type = fe->type; // Mark as being indirectly invoked. Direct invocations go through // BindCallTarget. @@ -1325,40 +1189,62 @@ bool Semantics::CheckSymbolExpr(SymbolExpr* expr, bool allow_types) { } switch (decl->ident()) { - case iVARIABLE: - expr->set_lvalue(true); - break; - case iFUNCTN: - // Not an l-value. - break; + case iVARIABLE: { + auto var_decl = decl->as(); + assert(var_decl); + + ir::Variable* var = nullptr; + if (var_decl->vclass() == sGLOBAL || var_decl->vclass() == sSTATIC) { + auto it = global_vars_.find(var_decl); + assert(it != global_vars_.end()); + var = it->second; + } else { + auto it = sc_->local_vars().find(var_decl); + assert(it != sc_->local_vars().end()); + var = it->second; + } + + return new ir::VariableRef(expr, QualType(type), var); + } + case iFUNCTN: { + auto fun = BuildFunction(decl->to()); + return BuildFunctionRef(expr, fun); + } case iTYPENAME: if (!allow_types) { report(expr, 174) << decl->name(); - return false; + return nullptr; } - break; + return new ir::TypeRef(expr, QualType(type)); case iCONSTEXPR: - val.set_constval(decl->ConstVal()); - break; + return new ir::Const(expr, QualType(type), decl->ConstVal()); default: // Should not be a symbol. assert(false); + return nullptr; } - return true; } -bool Semantics::CheckCommaExpr(CommaExpr* comma) { +ir::Value* Semantics::CheckCommaExpr(CommaExpr* comma) { AutoErrorPos aep(comma->pos()); - for (const auto& expr : comma->exprs()) { - if (!CheckRvalue(expr)) - return false; - } + // A single value acts as a passthrough. + if (comma->exprs().size() == 1) + return CheckExpr(comma->exprs()[0]); - Expr* last = comma->exprs().back(); - if (comma->exprs().size() > 1 && last->lvalue()) { - last = new RvalueExpr(last); - comma->exprs().back() = last; + std::vector values; + for (size_t i = 0; i < comma->exprs().size(); i++) { + auto expr = comma->exprs().at(i); + + // Don't bother converting ignored results to rvalues. + ir::Value* val; + if (i == comma->exprs().size() - 1) + val = CheckRvalue(expr); + else + val = CheckExpr(expr); + if (!val) + return nullptr; + values.emplace_back(val); } for (size_t i = 0; i < comma->exprs().size() - 1; i++) { @@ -1367,14 +1253,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 +1271,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 +1347,101 @@ IndexExpr::ProcessUses(SemaContext& sc) expr_->MarkAndProcessUses(sc); } -bool Semantics::CheckThisExpr(ThisExpr* expr) { - auto sym = expr->decl(); - assert(sym->ident() == iVARIABLE); - - auto& val = expr->val(); - val.ident = sym->ident(); - val.sym = sym; - val.set_type(sym->type()); - expr->set_lvalue(true); - return true; +ir::Value* Semantics::CheckThisExpr(ThisExpr* expr) { + return new ir::ThisRef(expr, QualType(expr->decl()->type())); } -bool Semantics::CheckNullExpr(NullExpr* expr) { - auto& val = expr->val(); - val.set_constval(0); - val.set_type(types_->type_null()); - return true; +ir::Value* Semantics::CheckNullExpr(NullExpr* expr) { + return new ir::Const(expr, types_->get_null(), 0); } -bool Semantics::CheckTaggedValueExpr(TaggedValueExpr* expr) { - auto& val = expr->val(); - val.set_type(expr->type()); - val.set_constval(expr->value()); - return true; +ir::Value* Semantics::CheckTaggedValueExpr(TaggedValueExpr* expr) { + return new ir::Const(expr, QualType(expr->type()), expr->value()); } -bool Semantics::CheckStringExpr(StringExpr* expr) { - auto& val = expr->val(); - val.ident = iEXPRESSION; - val.set_type(types_->defineArray(types_->type_char(), (cell)expr->text()->length() + 1)); - return true; +ir::Value* Semantics::CheckStringExpr(StringExpr* expr) { + auto type = types_->defineArray(types_->type_char(), (cell)expr->text()->length() + 1); + return new ir::CharArrayLiteral(expr, QualType(type)); } -bool Semantics::CheckFieldAccessExpr(FieldAccessExpr* expr, bool from_call) { +ir::Value* Semantics::CheckFieldAccessExpr(FieldAccessExpr* expr, bool from_call) { AutoErrorPos aep(expr->pos()); - auto base = expr->base(); - if (auto sym_expr = base->as()) { - if (!CheckSymbolExpr(sym_expr, true)) - return false; - } else { - if (!CheckRvalue(base)) - return false; - } + ir::Value* base = nullptr; + if (auto sym_expr = expr->base()->as()) + base = CheckSymbolExpr(sym_expr, true); + else + base = CheckRvalue(expr->base()); + if (!base) + return nullptr; int token = expr->token(); if (token == tDBLCOLON) return CheckStaticFieldAccessExpr(expr); - const auto& base_val = base->val(); - switch (base_val.ident) { - case iFUNCTN: - report(expr, 107); - return false; - default: - if (base_val.type()->isArray()) { - report(expr, 96) << expr->name() << "type" << "array"; - return false; - } - break; + if (base->type()->isFunction()) { + report(expr, 107); + return nullptr; + } + if (base->type()->isArray()) { + report(expr, 96) << expr->name() << "type" << "array"; + return nullptr; } - auto& val = expr->val(); - if (base_val.ident == iTYPENAME) { - auto map = MethodmapDecl::LookupMethodmap(base_val.sym); + if (auto type_ref = base->as()) { + auto map = type_ref->type()->asMethodmap(); auto member = map ? map->FindMember(expr->name()) : nullptr; if (!member || !member->as()) { - report(expr, 444) << base_val.sym->name() << expr->name(); - return false; + report(expr, 444) << type_ref->type().ptr() << expr->name(); + return nullptr; } auto method = member->as(); if (!method->is_static()) { report(expr, 176) << method->decl_name() << map->name(); - return false; + return nullptr; } - expr->set_resolved(method); - val.ident = iFUNCTN; - val.sym = method; - markusage(method, uREAD); - return true; + + auto fun = BuildFunction(method); + return BuildFunctionRef(expr, fun); } - Type* base_type = base_val.type(); + QualType base_type = base->type(); if (auto es = base_type->asEnumStruct()) - return CheckEnumStructFieldAccessExpr(expr, base_type, es, from_call); + return CheckEnumStructFieldAccessExpr(expr, base, es, from_call); if (base_type->isReference()) - base_type = base_type->inner(); + base_type = QualType(base_type->inner()); auto map = base_type->asMethodmap(); if (!map) { - report(expr, 104) << base_val.type(); - return false; + report(expr, 104) << base_type; + return nullptr; } auto member = map->FindMember(expr->name()); if (!member) { report(expr, 105) << map->name() << expr->name(); - return false; + return nullptr; } - if (auto prop = member->as()) { - // This is the only scenario in which we need to compute a load of the - // base address. Otherwise, we're only accessing the type. - if (base->lvalue()) - base = expr->set_base(new RvalueExpr(base)); - val.set_type(prop->property_type()); - val.set_accessor(prop); - expr->set_lvalue(true); - return true; - } + if (auto prop = member->as()) + return new ir::PropertyRef(expr, QualType(prop->property_type()), base, prop); auto method = member->as(); if (method->is_static()) { report(expr, 177) << method->decl_name() << map->name() << method->decl_name(); - return false; + return nullptr; } expr->set_resolved(method); if (!from_call) { report(expr, 50); - return false; + return nullptr; } - val.ident = iFUNCTN; - val.sym = method; - markusage(method, uREAD); - return true; + auto fun = BuildFunction(method); + + // :TODO: real fun type + return new ir::FunctionRef(expr, QualType(method->return_type()), fun); } void @@ -1638,7 +1450,7 @@ FieldAccessExpr::ProcessUses(SemaContext& sc) base_->MarkAndProcessUses(sc); } -FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { +ir::Function* Semantics::BindCallTarget(CallExpr* call, Expr* target) { AutoErrorPos aep(target->pos()); switch (target->kind()) { @@ -1673,7 +1485,7 @@ FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { base = expr->set_base(new RvalueExpr(base)); if (resolved->as() || !method->is_static()) call->set_implicit_this(base); - return val.sym->as()->canonical(); + return BuildFunction(val.sym->as()); } case ExprKind::SymbolExpr: { call->set_implicit_this(nullptr); @@ -1691,7 +1503,7 @@ FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { report(target, 170) << decl->name(); return nullptr; } - return mm->ctor(); + return BuildFunction(mm->ctor()); } auto fun = decl->as(); if (!fun) { @@ -1704,7 +1516,7 @@ FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { report(target, 4) << decl->name(); return nullptr; } - return fun; + return BuildFunction(fun); } default: report(target, 12); @@ -1712,7 +1524,7 @@ FunctionDecl* Semantics::BindCallTarget(CallExpr* call, Expr* target) { } } -FunctionDecl* Semantics::BindNewTarget(Expr* target) { +ir::Function* Semantics::BindNewTarget(Expr* target) { AutoErrorPos aep(target->pos()); switch (target->kind()) { @@ -1734,155 +1546,124 @@ FunctionDecl* Semantics::BindNewTarget(Expr* target) { report(expr, 172) << mm->name(); return nullptr; } - return mm->ctor(); + return BuildFunction(mm->ctor()); } } return nullptr; } -bool Semantics::CheckEnumStructFieldAccessExpr(FieldAccessExpr* expr, Type* type, EnumStructDecl* root, - bool from_call) +ir::Value* Semantics::CheckEnumStructFieldAccessExpr(FieldAccessExpr* expr, ir::Value* base, + EnumStructDecl* root, bool from_call) { - expr->set_resolved(FindEnumStructField(type, expr->name())); + auto type = base->type(); + + expr->set_resolved(FindEnumStructField(*type, expr->name())); auto field_decl = expr->resolved(); if (!field_decl) { report(expr, 105) << type << expr->name(); - return false; + return nullptr; } - auto& val = expr->val(); if (auto fun = field_decl->as()) { if (!from_call) { report(expr, 76); - return false; + return nullptr; } - val.ident = iFUNCTN; - val.sym = fun; - markusage(val.sym, uREAD); - return true; + auto ir_fun = BuildFunction(fun); + + // :TODO: real type + return new ir::FunctionRef(expr, QualType(fun->return_type()), ir_fun); } auto field = field_decl->as(); assert(field); - Type* field_type = field->type_info().type; - - val.set_type(field_type); - if (field_type->isArray()) { - // Already an r-value. - val.ident = iEXPRESSION; - } else { - // Need LOAD_I to convert to r-value. - val.ident = iARRAYCELL; - expr->set_lvalue(true); - } - return true; + QualType field_type = QualType(field->type_info().type); + return new ir::FieldRef(expr, field_type, base, field); } -bool Semantics::CheckStaticFieldAccessExpr(FieldAccessExpr* expr) { +ir::Value* Semantics::CheckStaticFieldAccessExpr(FieldAccessExpr* expr) { AutoErrorPos aep(expr->pos()); auto base = expr->base(); const auto& base_val = base->val(); if (base_val.ident != iTYPENAME) { report(expr, 108); - return false; + return nullptr; } Type* type = base_val.type(); Decl* field = FindEnumStructField(type, expr->name()); if (!field) { report(expr, 105) << type << expr->name(); - return false; + return nullptr; } auto fd = field->as(); if (!fd) { report(expr, 445) << field->name(); - return false; + return nullptr; } expr->set_resolved(field); - auto& val = expr->val(); - val.set_constval(fd->offset()); - val.set_type(types_->type_int()); - return true; + return new ir::Const(expr, types_->get_int(), fd->offset()); } -bool Semantics::CheckSizeofExpr(SizeofExpr* expr) { +ir::Value* Semantics::CheckSizeofExpr(SizeofExpr* expr) { AutoErrorPos aep(expr->pos()); Expr* child = expr->child(); - if (auto sym = child->as()) { - if (!CheckSymbolExpr(sym, true)) - return false; - } else { - if (!CheckExpr(child)) - return false; + ir::Value* val = nullptr; + if (auto sym = child->as()) + val = CheckSymbolExpr(sym, true); + else + val = CheckExpr(child); + if (!val) + return nullptr; + + if (auto type_ref = val->as()) { + auto es = type_ref->type()->asEnumStruct(); + if (!es) { + report(child, 72); + return nullptr; + } + return new ir::Const(expr, types_->get_int(), es->array_size()); } - auto& val = expr->val(); - val.set_type(types_->type_int()); - - const auto& cv = child->val(); - switch (cv.ident) { - case iARRAYCELL: - case iVARIABLE: - case iEXPRESSION: - if (auto es = cv.type()->asEnumStruct()) { - val.set_constval(es->array_size()); - } else if (auto array = cv.type()->as()) { - if (!array->size()) { - report(child, 163); - return false; - } - val.set_constval(array->size()); - } else if (cv.ident == iEXPRESSION) { + auto type = val->type(); + if (type->isReference()) + type = QualType(type->inner()); + + switch (type->kind()) { + case TypeKind::Builtin: + case TypeKind::Methodmap: + case TypeKind::Function: + case TypeKind::Object: + case TypeKind::FunctionSignature: + if (type->isVoid()) { report(child, 72); - return false; - } else { - val.set_constval(1); - report(expr, 252); + return nullptr; } - return true; - - case iARRAYCHAR: - report(expr, 252); - val.set_constval(1); - return true; + return new ir::Const(child, types_->get_int(), 1); - case iTYPENAME: { - auto es = cv.sym->as(); - if (!es) { - report(child, 72); - return false; - } - val.set_constval(es->array_size()); - return true; - } + case TypeKind::EnumStruct: + return new ir::Const(child, types_->get_int(), type->asEnumStruct()->array_size()); - case iCONSTEXPR: { - auto access = child->as(); - if (!access || access->token() != tDBLCOLON) { - report(child, 72); - return false; + case TypeKind::Array: { + auto array = type->as(); + if (!array->size()) { + report(child, 163); + return nullptr; } - auto field = access->resolved()->as(); - if (auto array = field->type()->as()) - val.set_constval(array->size()); - else if (auto es = field->type()->asEnumStruct()) - val.set_constval(es->array_size()); - else - val.set_constval(1); - return true; + return new ir::Const(child, types_->get_int(), array->size()); } default: report(child, 72); - return false; + return nullptr; } } @@ -1909,57 +1690,54 @@ DefaultArgExpr::DefaultArgExpr(const token_pos_t& pos, ArgDecl* arg) // accurately construct it. } -bool Semantics::CheckCallExpr(CallExpr* call) { +ir::Value* Semantics::CheckCallExpr(CallExpr* call) { AutoErrorPos aep(call->pos()); // Note: we do not Analyze the call target. We leave this to the // implementation of BindCallTarget. - FunctionDecl* fun; + ir::Function* fun; if (call->token() == tNEW) fun = BindNewTarget(call->target()); else fun = BindCallTarget(call, call->target()); if (!fun) - return false; - - assert(fun->canonical() == fun); - - call->set_fun(fun); + return nullptr; - if (fun->return_type()->isArray() || fun->return_type()->isEnumStruct()) { + FunctionDecl* decl = fun->decl(); + if (decl->return_type()->isArray() || decl->return_type()->isEnumStruct()) { // We need to know the size of the returned array. Recursively analyze // the function. - if (fun->is_analyzing() || !CheckFunctionDecl(fun)) { + if (decl->is_analyzing() || !CheckFunctionDecl(decl)) { report(call, 411); - return false; + return nullptr; } } - markusage(fun, uREAD); + fun_->AddReferenceTo(fun); - auto& val = call->val(); - val.ident = iEXPRESSION; - val.set_type(fun->return_type()); + assert(!decl->return_array()); +#if 0 if (fun->return_array()) NeedsHeapAlloc(call); +#endif // We don't have canonical decls yet, so get the one attached to the symbol. - if (fun->deprecate()) - report(call, 234) << fun->name() << fun->deprecate(); + if (decl->deprecate()) + report(call, 234) << decl->name() << decl->deprecate(); ParamState ps; unsigned int nargs = 0; unsigned int argidx = 0; - auto& arglist = fun->args(); + auto& arglist = decl->args(); if (call->implicit_this()) { if (arglist.empty()) { report(call->implicit_this(), 92); - return false; + return nullptr; } - Expr* param = CheckArgument(call, arglist[0], call->implicit_this(), &ps, 0); + ir::Value* param = CheckArgument(call, arglist[0], call->implicit_this(), &ps, 0); if (!param) - return false; + return nullptr; ps.argv[0] = param; nargs++; argidx++; @@ -1969,9 +1747,9 @@ bool Semantics::CheckCallExpr(CallExpr* call) { for (const auto& param : call->args()) { unsigned int argpos; if (auto named = param->as()) { - int pos = fun->FindNamedArg(named->name); + int pos = decl->FindNamedArg(named->name()); if (pos < 0) { - report(call, 17) << named->name; + report(call, 17) << named->name(); break; } argpos = pos; @@ -1979,28 +1757,28 @@ bool Semantics::CheckCallExpr(CallExpr* call) { } else { if (namedparams) { report(call, 44); // positional parameters must precede named parameters - return false; + return nullptr; } argpos = nargs; if (argidx >= arglist.size()) { report(param->pos(), 92); - return false; + return nullptr; } } if (argpos >= SP_MAX_CALL_ARGUMENTS) { report(call, 45); // too many function arguments - return false; + return nullptr; } if (argpos < ps.argv.size() && ps.argv[argpos]) { report(call, 58); // argument already set - return false; + return nullptr; } // Add the argument to |argv| and perform type checks. auto result = CheckArgument(call, arglist[argidx], param, &ps, argpos); if (!result) - return false; + return nullptr; ps.argv[argpos] = result; nargs++; @@ -2012,7 +1790,7 @@ bool Semantics::CheckCallExpr(CallExpr* call) { if (!sc_->func()) { report(call, 10); - return false; + return nullptr; } // Check for missing or invalid extra arguments, and fill in default @@ -2024,39 +1802,30 @@ bool Semantics::CheckCallExpr(CallExpr* call) { if (argidx >= ps.argv.size() || !ps.argv[argidx]) { auto result = CheckArgument(call, arg, nullptr, &ps, argidx); if (!result) - return false; + return nullptr; ps.argv[argidx] = result; } +#if 0 Expr* expr = ps.argv[argidx]; if (expr->as() && !IsReferenceType(iVARIABLE, arg->type())) { - UserOperation userop; - if (find_userop(*sc_, 0, arg->default_value()->type, arg->type(), 2, nullptr, - &userop)) - { - ps.argv[argidx] = new CallUserOpExpr(userop, expr); - } + assert(false); } +#endif } - // Copy newly deduced argument information. - if (call->args().size() == ps.argv.size()) { - for (size_t i = 0; i < ps.argv.size(); i++) - call->args()[i] = ps.argv[i]; - } else { - new (&call->args()) PoolArray(ps.argv); - } - return true; + return new ir::CallOp(call, QualType(decl->return_type()), fun, ps.argv); } -Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, - ParamState* ps, unsigned int pos) +ir::Value* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, + ParamState* ps, unsigned int pos) { while (pos >= ps->argv.size()) ps->argv.push_back(nullptr); unsigned int visual_pos = call->implicit_this() ? pos : pos + 1; +#if 0 if (!param || param->as()) { if (arg->type_info().is_varargs) { report(call, 92); // argument count mismatch @@ -2082,9 +1851,11 @@ Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, // The rest of the code to handle default values is in DoEmit. return param; } +#endif + ir::Value* val = nullptr; if (param != call->implicit_this()) { - if (!CheckRvalue(param)) + if (val = CheckExpr(param); !val) return nullptr; } @@ -2093,37 +1864,28 @@ Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, bool handling_this = call->implicit_this() && (pos == 0); if (param->val().ident == iACCESSOR) { - if (!CheckRvalue(param->pos(), param->val())) +#if 0 + if (val = CheckRvalue(val); !val) return nullptr; - param = new RvalueExpr(param); +#endif + assert(false); } - const auto* val = ¶m->val(); - bool lvalue = param->lvalue(); if (arg->type_info().is_varargs) { assert(!handling_this); // Always pass by reference. - if (val->ident == iVARIABLE) { - if (val->sym->is_const() && !arg->type_info().is_const) { - // Treat a "const" variable passed to a function with a - // non-const "variable argument list" as a constant here. - if (!lvalue) { - report(param, 22); // need lvalue - return nullptr; - } - NeedsHeapAlloc(param); - } else if (!lvalue) { - NeedsHeapAlloc(param); - } - } else if (val->ident == iCONSTEXPR || val->ident == iEXPRESSION) { + if (!val->type()->isReferenceType() && !val->as()) { NeedsHeapAlloc(param); + val = new ir::TempRef(param, val); } - if (!checktag_string(arg->type(), val) && !checktag(arg->type(), val->type())) + if (!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); + assert(false); +#if 0 if (!lvalue || val->ident == iARRAYCHAR) { report(param, 35) << visual_pos; // argument type mismatch return nullptr; @@ -2133,27 +1895,34 @@ Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, return nullptr; } checktag(arg->type()->inner(), val->type()); +#endif } 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 - TypeChecker tc(param, arg->type(), type, TypeChecker::Argument); + if (auto lval = val->as()) + val = BuildRvalue(param, lval); + + 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 = new ir::Load(param, val->type(), lval); +#if 0 // Do not allow user operators to transform |this|. UserOperation userop; if (!handling_this && @@ -2166,8 +1935,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 +1955,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 +2007,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 +2018,7 @@ bool Semantics::CheckNewArrayExprForArrayInitializer(NewArrayExpr* na) { assert(na->type()->isArray()); na->set_analysis_result(true); - return true; +#endif } void @@ -2259,22 +2029,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 +2074,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(), false); + if (!val) return false; - if (!expr->HasSideEffects()) - report(expr, 215); + + if (!val->HasSideEffects()) + report(stmt, 215); + + ir_->emplace(stmt, val); return true; } @@ -2397,7 +2182,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 +2204,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 +2224,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 +2241,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 +2254,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 +2310,25 @@ bool Semantics::CheckCompoundReturnStmt(ReturnStmt* stmt) { } bool Semantics::CheckAssertStmt(AssertStmt* stmt) { - if (Expr* expr = AnalyzeForTest(stmt->expr())) { - stmt->set_expr(expr); - return true; - } - return false; + auto val = AnalyzeForTest(stmt->expr()); + if (!val) + return false; + + ir_->emplace(stmt, val); + return true; } bool Semantics::CheckDeleteStmt(DeleteStmt* stmt) { - auto expr = stmt->expr(); - if (!CheckRvalue(expr)) + auto val = CheckExpr(stmt->expr()); + if (!val) return false; - const auto& v = expr->val(); - switch (v.ident) { - case iFUNCTN: - report(expr, 167) << "function"; - return false; - - case iVARIABLE: - if (v.type()->isArray() || v.type()->isEnumStruct()) { - report(expr, 167) << v.type(); - return false; - } - break; - - case iACCESSOR: - if (v.accessor()->getter()) - markusage(v.accessor()->getter(), uREAD); - if (v.accessor()->setter()) - markusage(v.accessor()->setter(), uREAD); - break; - } - - Type* type = v.type(); - if (type->isReference()) - type = type->inner(); - - if (type->isInt()) { - report(expr, 167) << "integers"; - return false; - } + MarkUsage(val, uREAD | uWRITTEN); + auto type = BuildRvalueType(val->type()); auto map = type->asMethodmap(); if (!map) { - report(expr, 115) << "type" << v.type(); + report(stmt, 115) << "type" << type; return false; } @@ -2578,51 +2340,46 @@ bool Semantics::CheckDeleteStmt(DeleteStmt* stmt) { } if (!map || !map->dtor()) { - report(expr, 115) << "methodmap" << map->name(); + report(stmt, 115) << "methodmap" << map->name(); return false; } markusage(map->dtor(), uREAD); - stmt->set_map(map); + ir_->emplace(stmt, val, map->dtor()); return true; } bool Semantics::CheckExitStmt(ExitStmt* stmt) { - auto expr = stmt->expr(); - if (!CheckRvalue(expr)) + auto val = CheckRvalue(stmt->expr()); + if (!val) return false; - if (expr->lvalue()) - expr = stmt->set_expr(new RvalueExpr(expr)); - if (!IsValueKind(expr->val().ident)) { - report(expr, 106); + AutoErrorPos aep(stmt->pos()); + if (!TypeChecker::DoCoerce(stmt->expr()->pos(), types_->get_int(), val->type())) return false; - } - - AutoErrorPos aep(expr->pos()); - if (!TypeChecker::DoCoerce(types_->type_int(), expr)) - return false; + ir_->emplace(stmt, val); return true; } bool Semantics::CheckDoWhileStmt(DoWhileStmt* stmt) { + ir::Value* cond; { ke::SaveAndSet restore_heap(&pending_heap_allocation_, false); - if (Expr* expr = AnalyzeForTest(stmt->cond())) { - stmt->set_cond(expr); - AssignHeapOwnership(expr); - } + if ((cond = AnalyzeForTest(stmt->cond())) == nullptr) + return false; +#if 0 + AssignHeapOwnership(stmt->cond()); +#endif } - auto cond = stmt->cond(); - ke::Maybe constval; - if (cond->val().ident == iCONSTEXPR) - constval.init(cond->val().constval()); + if (auto cv = cond->as()) + constval.init(cv->value()); + ir::InsnBlock* body; bool has_break = false; bool has_return = false; ke::Maybe always_returns; @@ -2631,8 +2388,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 +2413,46 @@ bool Semantics::CheckDoWhileStmt(DoWhileStmt* stmt) { } // :TODO: endless loop warning? + ir_->emplace(stmt, cond, body); return true; } bool Semantics::CheckForStmt(ForStmt* stmt) { bool ok = true; - if (stmt->init() && !CheckStmt(stmt->init())) - ok = false; - auto cond = stmt->cond(); - if (cond) { - if (Expr* expr = AnalyzeForTest(cond)) - cond = stmt->set_cond(expr); + ir::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::Value* advance = nullptr; if (stmt->advance()) { ke::SaveAndSet restore(&pending_heap_allocation_, false); - if (CheckRvalue(stmt->advance())) + if ((advance = CheckExpr(stmt->advance(), false)) != nullptr) { + // :TODO: check if has effect +#if 0 AssignHeapOwnership(stmt->advance()); - else +#endif + } else { ok = false; + } } ke::Maybe constval; - if (cond && cond->val().ident == iCONSTEXPR) - constval.init(cond->val().constval()); + if (auto cv = cond->as()) + constval.init(cv->value()); + ir::InsnBlock* body_ir; bool has_break = false; bool has_return = false; ke::Maybe always_returns; @@ -2690,7 +2462,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 +2497,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 +2527,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 +2584,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 +2649,24 @@ bool Semantics::CheckFunctionDeclImpl(FunctionDecl* info) { report(info->pos(), 141); } + auto fun = BuildFunction(info); + ke::SaveAndSet push_fun(&fun_, fun); + + bool ok = true; + for (const auto& arg : fun->argv()) { + assert(sc_->local_vars().find(arg->decl()) == sc_->local_vars().end()); + sc_->local_vars().emplace(arg->decl(), arg); + + ok &= CheckFunctionArgument(arg); + } + + if (info->body()) { + ir::NodeListBuilder builder(&ir_); + ok = CheckStmt(info->body(), STMT_OWNS_HEAP); + fun->set_body(builder.Finish()); + } + +#if 0 if (info->is_native()) { if (decl.type.dim_exprs.size() > 0) { report(info->pos(), 83); @@ -2916,6 +2728,47 @@ bool Semantics::CheckFunctionDeclImpl(FunctionDecl* info) { if (info->is_public()) cc_.publics().emplace(info->canonical()); return ok; +#endif + return ok; +} + +ir::Function* Semantics::BuildFunction(FunctionDecl* decl) { + auto canonical = decl->canonical(); + + if (auto iter = functions_.find(canonical); iter != functions_.end()) + return iter->second; + + // Build arguments. + PoolArray argv(canonical->args().size()); + for (size_t i = 0; i < canonical->args().size(); i++) { + const auto& arg = canonical->args().at(i); + argv[i] = new ir::Argument(arg); + } + + auto fun = new ir::Function(canonical); + fun->set_argv(std::move(argv)); + + functions_.emplace(canonical, fun); + mod_->functions().emplace_back(fun); + return fun; +} + +ir::FunctionRef* Semantics::BuildFunctionRef(Expr* expr, ir::Function* fun) { + fun_->AddReferenceTo(fun); +#if 0 + // :TODO: proper type +#endif + return new ir::FunctionRef(expr, QualType(fun->decl()->return_type()), fun); +} + +ir::FunctionRef* Semantics::BuildFunctionRef(Expr* expr, FunctionDecl* decl) { + auto fun = BuildFunction(decl); + return BuildFunctionRef(expr, fun); +} + +bool Semantics::CheckFunctionArgument(ir::Argument* arg) { + assert(!arg->arg_decl()->init()); + return true; } void Semantics::CheckFunctionReturnUsage(FunctionDecl* info) { @@ -3066,10 +2919,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 +3004,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 +3032,21 @@ SymbolScope* Semantics::current_scope() const { // Determine the set of live functions. void Semantics::DeduceLiveness() { - std::vector work; - std::unordered_set seen; + std::vector work; + std::unordered_set seen; // The root set is all public functions. - for (const auto& decl : cc_.publics()) { - assert(!decl->is_native()); - assert(decl->is_public()); + for (const auto& fun : mod_->functions()) { + if (fun->decl()->is_public()) + fun->set_is_live(); - decl->set_is_live(); - - seen.emplace(decl); - work.emplace_back(decl); + seen.emplace(fun); + work.emplace_back(fun); } // Traverse referrers to find the transitive set of live functions. while (!work.empty()) { - FunctionDecl* live = ke::PopBack(&work); + auto live = ke::PopBack(&work); if (!live->refers_to()) continue; @@ -3234,26 +3086,181 @@ void Semantics::DeduceMaybeUsed() { } } -bool Semantics::CheckRvalue(Expr* expr) { - if (!CheckExpr(expr)) - return false; - return CheckRvalue(expr->pos(), expr->val()); +QualType Semantics::BuildRvalueType(QualType type) { + if (type->isReference()) + return QualType(type->inner()); + return type; +} + +ir::Value* Semantics::BuildRvalue(Expr* expr, ir::Lvalue* lval) { + QualType type = lval->type(); + if (lval->as()) + type = BuildRvalueType(type); + + assert(!type->isReference()); + assert(!lval->as()); + return new ir::Load(expr, type, lval); +} + +ir::Value* Semantics::CheckRvalue(Expr* expr, bool used) { + auto val = CheckExpr(expr, used); + if (!val) + return nullptr; + + if (auto lval = val->as()) + return BuildRvalue(expr, lval); + + return val; } -bool Semantics::CheckRvalue(const token_pos_t& pos, const value& val) { - if (auto accessor = val.accessor()) { - if (!accessor->getter()) { - report(pos, 149) << accessor->name(); +static inline std::string_view GetOverloadName(int token) { + switch (token) { + case '=': return "="; + case '*': return "*"; + case taMULT: return "*"; + case '/': return "/"; + case taDIV: return "/"; + case '%': return "%"; + case taMOD: return "%"; + case '+': return "+"; + case taADD: return "+"; + case '-': return "-"; + case taSUB: return "-"; + case '<': return "<"; + case '>': return ">"; + case tlLE: return "<="; + case tlGE: return ">="; + case tlEQ: return "=="; + case tlNE: return "!="; + case tINC: return "++"; + case tDEC: return "--"; + case '!': return "!"; + default: return {}; + } +} + + +static inline bool MatchOperator(int token, FunctionDecl* fun, QualType type1, QualType type2) { + // If token is '=', type2 is the return type. + size_t numparam = (type2 && token != '=') ? 2 : 1; + + const auto& args = fun->args(); + if (args.size() != numparam) + return false; + + assert(numparam == 1 || numparam == 2); + QualType types[2] = { type1, type2 }; + + for (size_t i = 0; i < numparam; i++) { + if (args[i]->type_info().is_varargs) + return false; + if (QualType(args[i]->type_info().type) != types[i]) return false; - } } + + if (token == '=' && QualType(fun->return_type()) != type2) + return false; return true; } +ir::Value* Semantics::MaybeCallUserOp(Expr* expr, int token, ir::Value* first, + ir::Value* second) +{ + // No operator overloading allowed in the preprocessor. + if (cc_.in_preprocessor()) + return nullptr; + + // No overloads for int,int. + QualType type1 = first->type(); + QualType type2; + if (second) + type2 = second->type(); + + // :TODO: use TypeChecker to do this instead + if (type1->isReference()) + type1 = QualType(type1->inner()); + if (type2 && type2->isReference()) + type2 = QualType(type2->inner()); + + if (type1->isInt() || (type2 && type2->isInt())) + return nullptr; + + auto opername = GetOverloadName(token); + if (opername.empty()) + return nullptr; + + auto atom = cc_.atom(opername); + Decl* chain = FindSymbol(*sc_, atom); + if (!chain) + return nullptr; + + FunctionDecl* decl = nullptr; + bool swapparams = false; + bool is_commutative = IsOperTokenCommutative(token); + for (auto iter = chain; iter; iter = iter->next) { + auto fun = iter->as(); + if (!fun) + continue; + fun = fun->canonical(); + + bool matched = MatchOperator(token, fun, type1, type2); + bool swapped = false; + if (!matched && is_commutative && type1 != type2 && token) { + matched = MatchOperator(token, fun, type2, type1); + swapped = true; + } + if (matched) { + decl = fun; + swapparams = swapped; + break; + } + } + + if (!decl) + return nullptr; + + // we don't want to use the redefined operator in the function that + // redefines the operator itself, otherwise the snippet below gives + // an unexpected recursion: + // fixed:operator+(fixed:a, fixed:b) + // return a + b + if (decl == sc_->func()) + report(expr, 408); + + markusage(decl, uREAD); + + return new ir::CallUserOp(expr, QualType(decl->return_type()), decl, first, second, + swapparams); +} + void DeleteStmt::ProcessUses(SemaContext& sc) { expr_->MarkAndProcessUses(sc); markusage(map_->dtor(), uREAD); } +void Semantics::MarkUsage(ir::Value* val, uint8_t usage) { + switch (val->kind()) { + case IrKind::PropertyRef: { + auto prop = val->to(); + if (usage & uREAD) + markusage(prop->decl()->getter(), uREAD); + if (usage & uWRITTEN) + markusage(prop->decl()->setter(), uWRITTEN); + break; + } + case IrKind::VariableRef: { + auto ref = val->to(); + markusage(ref->var()->decl(), usage); + break; + } + case IrKind::FunctionRef: { + assert(false); + break; + } + default: + break; + } +} + } // namespace cc } // namespace sp diff --git a/compiler/semantics.h b/compiler/semantics.h index d5518f100..80b175c38 100644 --- a/compiler/semantics.h +++ b/compiler/semantics.h @@ -24,6 +24,7 @@ #include #include "compile-context.h" +#include "ir.h" #include "sc.h" #include "scopes.h" #include "parse-node.h" @@ -97,6 +98,7 @@ class SemaContext bool& loop_has_break() { return loop_has_break_; } bool& loop_has_continue() { return loop_has_continue_; } bool& loop_has_return() { return loop_has_return_; } + FlowType& flow_type() { return flow_type_; } bool warned_unreachable() const { return warned_unreachable_; } void set_warned_unreachable() { warned_unreachable_ = true; } @@ -118,6 +120,7 @@ class SemaContext bool preprocessing() const { return preprocessing_; } std::unordered_set& static_scopes() { return static_scopes_; } + std::unordered_map& local_vars() { return local_vars_; } private: CompileContext& cc_; @@ -135,7 +138,9 @@ class SemaContext bool warned_unreachable_ = false; bool preprocessing_ = false; SemaContext* cc_prev_sc_ = nullptr; + FlowType flow_type_ = Flow_None; std::unordered_set static_scopes_; + std::unordered_map local_vars_; }; class Semantics final @@ -148,7 +153,7 @@ class Semantics final friend class Parser; public: - explicit Semantics(CompileContext& cc); + Semantics(CompileContext& cc, std::shared_ptr mod); bool Analyze(ParseTree* tree); @@ -171,75 +176,81 @@ 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 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, bool used = true); + ir::Value* CheckNewArrayExpr(NewArrayExpr* expr); + ir::Value* CheckArrayExpr(ArrayExpr* expr); + ir::Value* CheckStringExpr(StringExpr* expr); + ir::Value* CheckTaggedValueExpr(TaggedValueExpr* expr); + ir::Value* CheckNullExpr(NullExpr* expr); + ir::Value* CheckThisExpr(ThisExpr* expr); + ir::Value* CheckCommaExpr(CommaExpr* expr); + ir::Value* CheckIndexExpr(IndexExpr* expr); + ir::Value* CheckCallExpr(CallExpr* expr); + ir::Value* CheckSymbolExpr(SymbolExpr* expr, bool allow_types); + ir::Value* CheckSizeofExpr(SizeofExpr* expr); + ir::Value* CheckCastExpr(CastExpr* expr); + ir::Value* CheckIncDecExpr(IncDecExpr* expr, bool used); + ir::Value* CheckTernaryExpr(TernaryExpr* expr); + ir::Value* CheckChainedCompareExpr(ChainedCompareExpr* expr); + ir::Value* CheckLogicalExpr(LogicalExpr* expr); + ir::Value* CheckBinaryExpr(BinaryExpr* expr); + ir::Value* CheckUnaryExpr(UnaryExpr* expr); + ir::Value* CheckFieldAccessExpr(FieldAccessExpr* expr, bool from_call); + ir::Value* CheckStaticFieldAccessExpr(FieldAccessExpr* expr); + ir::Value* CheckEnumStructFieldAccessExpr(FieldAccessExpr* expr, ir::Value* base, + EnumStructDecl* root, bool from_call); + ir::Value* CheckRvalue(Expr* expr, bool used = true); + ir::Value* BuildRvalue(Expr* expr, ir::Lvalue* val); + QualType BuildRvalueType(QualType type); + ir::FunctionRef* BuildFunctionRef(Expr* expr, ir::Function* fun); + ir::FunctionRef* BuildFunctionRef(Expr* expr, FunctionDecl* decl); + void MarkUsage(ir::Value* val, uint8_t usage); + + bool CheckAssignmentLHS(BinaryExpr* expr, ir::Lvalue* lval); + bool CheckAssignmentRHS(BinaryExpr* expr, ir::Lvalue* lval, ir::Value* rval); + 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::Function* BindNewTarget(Expr* target); + ir::Function* BindCallTarget(CallExpr* call, Expr* target); void NeedsHeapAlloc(Expr* expr); void AssignHeapOwnership(ParseNode* node); - Expr* AnalyzeForTest(Expr* expr); + ir::Value* AnalyzeForTest(Expr* expr); + ir::Value* MaybeCallUserOp(Expr* expr, int token, ir::Value* first, ir::Value* second); + ir::Function* BuildFunction(FunctionDecl* decl); void DeduceLiveness(); void DeduceMaybeUsed(); @@ -250,6 +261,7 @@ class Semantics final void CheckVoidDecl(const declinfo_t* decl, int variable); bool CheckScalarType(Expr* expr); + bool CheckScalarType(Expr* expr, QualType type); private: CompileContext& cc_; @@ -257,7 +269,12 @@ class Semantics final tr::unordered_set static_scopes_; tr::vector maybe_used_; SemaContext* sc_ = nullptr; + ir::Function* fun_ = nullptr; + std::shared_ptr mod_; + std::unordered_map functions_; + std::unordered_map global_vars_; bool pending_heap_allocation_ = false; + ir::NodeListBuilder* ir_ = nullptr; }; class AutoEnterScope final diff --git a/compiler/smx-assembly-buffer.h b/compiler/smx-assembly-buffer.h index 82762a2f6..0e3005858 100644 --- a/compiler/smx-assembly-buffer.h +++ b/compiler/smx-assembly-buffer.h @@ -17,12 +17,14 @@ // SourcePawn. If not, see http://www.gnu.org/licenses/. #pragma once -#include "shared/byte-buffer.h" #include #include #include "label.h" +#include "ir.h" +#include "parse-node.h" #include "sctracker.h" +#include "shared/byte-buffer.h" #include "symbols.h" namespace sp { @@ -171,14 +173,15 @@ class SmxAssemblyBuffer : public ByteBuffer } } - void copyarray(VarDeclBase* sym, cell size) { - if (sym->type()->isArray()) { - assert(sym->vclass() == sLOCAL || sym->vclass() == sARGUMENT); // symbol must be stack relative - emit(OP_LOAD_S_ALT, sym->addr()); - } else if (sym->vclass() == sLOCAL || sym->vclass() == sARGUMENT) { - emit(OP_ADDR_ALT, sym->addr()); + void copyarray(ir::Variable* var, cell size) { + auto decl = var->decl(); + if (decl->type()->isArray()) { + assert(decl->vclass() == sLOCAL || decl->vclass() == sARGUMENT); // symbol must be stack relative + emit(OP_LOAD_S_ALT, var->addr()); + } else if (decl->vclass() == sLOCAL || decl->vclass() == sARGUMENT) { + emit(OP_ADDR_ALT, var->addr()); } else { - emit(OP_CONST_ALT, sym->addr()); + emit(OP_CONST_ALT, var->addr()); } emit(OP_MOVS, size); } diff --git a/compiler/type-checker.cpp b/compiler/type-checker.cpp index 888da959b..368137ff4 100644 --- a/compiler/type-checker.cpp +++ b/compiler/type-checker.cpp @@ -309,7 +309,11 @@ bool TypeChecker::DoCoerce(Type* formal, Expr* actual) { } bool TypeChecker::DoCoerce(const token_pos_t& pos, Type* formal, Type* actual, Flags flags) { - TypeChecker tc(pos, QualType(formal), QualType(actual), Generic, flags); + return DoCoerce(pos, QualType(formal), QualType(actual), flags); +} + +bool TypeChecker::DoCoerce(const token_pos_t& pos, QualType formal, QualType actual, Flags flags) { + TypeChecker tc(pos, formal, actual, Generic, flags); return tc.Coerce(); } diff --git a/compiler/type-checker.h b/compiler/type-checker.h index a990b927f..fb657d9db 100644 --- a/compiler/type-checker.h +++ b/compiler/type-checker.h @@ -64,6 +64,7 @@ class TypeChecker { static bool DoCoerce(Type* formal, Expr* actual); static bool DoCoerce(const token_pos_t& pos, Type* formal, Type* actual, Flags flags = None); + static bool DoCoerce(const token_pos_t& pos, QualType formal, QualType actual, Flags flags = None); private: bool CheckImpl(); diff --git a/compiler/types.h b/compiler/types.h index 3aff46afa..4d74fa524 100644 --- a/compiler/types.h +++ b/compiler/types.h @@ -306,6 +306,10 @@ class Type : public PoolObject return pstruct_ptr_; } + bool isReferenceType() const { + return isArray() || isEnumStruct() || isReference(); + } + Type* inner() const { assert(isReference() || isArray()); return inner_type_; @@ -439,6 +443,17 @@ class TypeManager Type* type_char() const { return type_string_; } Type* type_int() const { return type_int_; } + QualType get_object() const { return QualType(type_object_); } + QualType get_null() const { return QualType(type_null_); } + QualType get_function() const { return QualType(type_function_); } + QualType get_any() const { return QualType(type_any_); } + QualType get_void() const { return QualType(type_void_); } + QualType get_float() const { return QualType(type_float_); } + QualType get_bool() const { return QualType(type_bool_); } + QualType get_string() const { return QualType(type_string_); } + QualType get_char() const { return QualType(type_string_); } + QualType get_int() const { return QualType(type_int_); } + private: Type* add(const char* name, TypeKind kind); Type* add(Atom* name, TypeKind kind); diff --git a/shared/string-pool.h b/shared/string-pool.h index 1a953283b..b16bf2384 100644 --- a/shared/string-pool.h +++ b/shared/string-pool.h @@ -19,6 +19,7 @@ #define _include_jitcraft_string_pool_h_ #include +#include #include #include @@ -31,33 +32,6 @@ namespace sp { using namespace ke; -class CharsAndLength -{ - public: - CharsAndLength() - : str_(nullptr), - length_(0) - { - } - - CharsAndLength(const char* str, size_t length) - : str_(str), - length_(length) - { - } - - const char* str() const { - return str_; - } - size_t length() const { - return length_; - } - - private: - const char* str_; - size_t length_; -}; - class StringPool { public: @@ -75,18 +49,18 @@ class StringPool delete* i; } - Atom* add(const std::string& str) { - return add(str.c_str(), str.size()); - } - Atom* add(const char* str, size_t length) { - CharsAndLength chars(str, length); + std::string_view chars(str, length); Table::Insert p = table_.findForAdd(chars); if (!p.found() && !table_.add(p, new Atom(str, length))) return nullptr; return *p; } + Atom* add(std::string_view sv) { + return add(sv.data(), sv.size()); + } + Atom* add(const char* str) { return add(str, strlen(str)); } @@ -99,14 +73,14 @@ class StringPool return HashCharSequence(key, strlen(key)); } - static uint32_t hash(const CharsAndLength& key) { - return HashCharSequence(key.str(), key.length()); + static uint32_t hash(const std::string_view& key) { + return HashCharSequence(key.data(), key.size()); } - static bool matches(const CharsAndLength& key, const Payload& e) { - if (key.length() != e->length()) + static bool matches(const std::string_view& key, const Payload& e) { + if (key.size() != e->length()) return false; - return strncmp(key.str(), e->chars(), key.length()) == 0; + return strncmp(key.data(), e->chars(), key.size()) == 0; } }; diff --git a/third_party/amtl b/third_party/amtl index e38cfe3cb..2d3b1a337 160000 --- a/third_party/amtl +++ b/third_party/amtl @@ -1 +1 @@ -Subproject commit e38cfe3cbf2047916e4a68017840bd325a8f7080 +Subproject commit 2d3b1a3378a3728637f26660c9ffc2df3189cf62 diff --git a/vm/method-verifier.cpp b/vm/method-verifier.cpp index d305a26a6..cb7619ba4 100644 --- a/vm/method-verifier.cpp +++ b/vm/method-verifier.cpp @@ -86,6 +86,7 @@ MethodVerifier::verify() cip_ = reinterpret_cast(block_->start()); while (cip_ < reinterpret_cast(block_->end())) { insn_ = cip_; + //SpewOpcode(stderr, rt_, method_, cip_); OPCODE op = (OPCODE)*cip_++; if (!verifyOp(op)) return nullptr; diff --git a/vm/opcodes.cpp b/vm/opcodes.cpp index b5f00fc60..96ebcb7b9 100644 --- a/vm/opcodes.cpp +++ b/vm/opcodes.cpp @@ -25,8 +25,6 @@ * this exception to all derivative works. AlliedModders LLC defines further * exceptions), found in LICENSE.txt _(as of this writing), version JULY-31-2007)), * or . - * - * Version: $Id$ */ #include "opcodes.h"