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/ast-types.h b/compiler/ast-types.h index bc9d81701..b0f399f6c 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,47 @@ FOR_EACH(StructExpr) \ FOR_EACH(StructInitFieldExpr) +#define IR_NODE_TYPE_LIST(FOR_EACH) \ + /* Decls */ \ + FOR_EACH(Variable) \ + /* Statements */ \ + FOR_EACH(Return) \ + FOR_EACH(ValueInsn) \ + FOR_EACH(Exit) \ + FOR_EACH(Break) \ + FOR_EACH(Continue) \ + FOR_EACH(Assert) \ + FOR_EACH(If) \ + FOR_EACH(DoWhile) \ + FOR_EACH(Delete) \ + FOR_EACH(ForLoop) \ + FOR_EACH(Switch) \ + FOR_EACH(FunctionDef) \ + /* Values */ \ + FOR_EACH(ConstVal) \ + FOR_EACH(CharArrayLiteral) \ + FOR_EACH(VariableRef) \ + FOR_EACH(TypeRef) \ + FOR_EACH(FunctionRef) \ + FOR_EACH(IndexOp) \ + FOR_EACH(Load) \ + FOR_EACH(TernaryOp) \ + FOR_EACH(BinaryOp) \ + FOR_EACH(Array) \ + FOR_EACH(CommaOp) \ + FOR_EACH(CallOp) \ + FOR_EACH(TempRef) \ + FOR_EACH(PropertyRef) \ + FOR_EACH(FieldRef) \ + FOR_EACH(UnaryOp) \ + FOR_EACH(CallUserOp) \ + FOR_EACH(IncDecOp) \ + FOR_EACH(Store) \ + FOR_EACH(ThisRef) + +namespace sp { +namespace cc { + enum class ExprKind : uint8_t { #define _(Name) Name, @@ -92,3 +135,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..16f57cf98 100644 --- a/compiler/code-generator.cpp +++ b/compiler/code-generator.cpp @@ -526,7 +526,7 @@ CodeGenerator::EmitExpr(Expr* expr) EmitNewArrayExpr(expr->to()); break; case ExprKind::NamedArgExpr: - EmitExpr(expr->to()->expr); + EmitExpr(expr->to()->expr()); break; default: @@ -768,7 +768,7 @@ CodeGenerator::EmitBinaryInner(int oper_tok, const UserOperation& in_user_op, Ex // 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)) { + if (IsOperTokenCommutative(oper_tok)) { __ const_alt(right_val.constval()); user_op.swapparams ^= true; } else { diff --git a/compiler/compile-context.h b/compiler/compile-context.h index c7784bd6c..2ca123cb6 100644 --- a/compiler/compile-context.h +++ b/compiler/compile-context.h @@ -22,6 +22,7 @@ #include #include +#include #include #include "array-data.h" @@ -74,15 +75,15 @@ class CompileContext final TypeManager* types() const { return types_.get(); } StringPool* atoms() { return &atoms_; } - Atom* atom(const std::string& str) { - return atoms_.add(str); - } Atom* atom(const char* str, size_t length) { return atoms_.add(str, length); } Atom* atom(const char* str) { return atoms_.add(str); } + Atom* atom(std::string_view sv) { + return atoms_.add(sv); + } const std::string& default_include() const { return default_include_; } void set_default_include(const std::string& file) { default_include_ = file; } diff --git a/compiler/expressions.cpp b/compiler/expressions.cpp index 9082bbe16..38742e530 100644 --- a/compiler/expressions.cpp +++ b/compiler/expressions.cpp @@ -40,168 +40,6 @@ namespace sp { namespace cc { -/* Function addresses of binary operators for signed operations */ -static const int op1[17] = { - // hier3 - '*', '/', '%', - // hier4 - '+', '-', - // hier5 - tSHL, tSHR, tSHRU, - // hier6 - '&', - // hier7 - '^', - // hier8 - '|', - // hier9 - tlLE, tlGE, '<', '>', - // hier10 - tlEQ, tlNE -}; - -static inline bool MatchOperator(int oper, FunctionDecl* fun, Type* type1, Type* type2, - int numparam) -{ - if (!oper) - numparam = 1; - - const auto& args = fun->args(); - if (args.size() != size_t(numparam)) - return false; - - assert(numparam == 1 || numparam == 2); - Type* types[2] = { type1, type2 }; - - for (int i = 0; i < numparam; i++) { - if (args[i]->type_info().is_varargs) - return false; - if (args[i]->type_info().type != types[i]) - return false; - } - - if (!oper && fun->type() != type2) - return false; - return true; -} - -bool find_userop(SemaContext& sc, int oper, Type* type1, Type* type2, int numparam, - const value* lval, UserOperation* op) -{ - static const char* binoperstr[] = {"*", "/", "%", "+", "-", "", "", "", "", - "", "", "<=", ">=", "<", ">", "==", "!="}; - static const bool binoper_savepri[] = {false, false, false, false, false, false, false, false, - false, false, false, true, true, true, true, false, - false}; - static const char* unoperstr[] = {"!", "-", "++", "--"}; - static const int unopers[] = {'!', '-', tINC, tDEC}; - - char opername[4] = ""; - size_t i; - bool savepri, savealt; - - if (type1->isReference()) - type1 = type1->inner(); - if (type2 && type2->isReference()) - type2 = type2->inner(); - - /* since user-defined operators on untagged operands are forbidden, we have - * a quick exit. - */ - assert(numparam == 1 || numparam == 2); - if (sc.cc().in_preprocessor()) - return false; - if (type1->isInt() && (numparam == 1 || type2->isInt())) - return false; - - savepri = savealt = false; - /* find the name with the operator */ - if (numparam == 2) { - if (oper == 0) { - /* assignment operator: a special case */ - strcpy(opername, "="); - if (lval != NULL && (lval->ident == iARRAYCELL || lval->ident == iARRAYCHAR)) - savealt = true; - } else { - assert((sizeof binoperstr / sizeof binoperstr[0]) == (sizeof op1 / sizeof op1[0])); - for (i = 0; i < sizeof op1 / sizeof op1[0]; i++) { - if (oper == op1[i]) { - strcpy(opername, binoperstr[i]); - savepri = binoper_savepri[i]; - break; - } - } - } - } else { - assert(oper); - assert(numparam == 1); - /* try a select group of unary operators */ - assert((sizeof unoperstr / sizeof unoperstr[0]) == (sizeof unopers / sizeof unopers[0])); - if (opername[0] == '\0') { - for (i = 0; i < sizeof unopers / sizeof unopers[0]; i++) { - if (oper == unopers[i]) { - strcpy(opername, unoperstr[i]); - break; - } - } - } - } - /* if not found, quit */ - if (opername[0] == '\0') - return false; - - // :TODO: restrict this to globals. - auto opername_atom = sc.cc().atom(opername); - Decl* chain = FindSymbol(sc, opername_atom); - if (!chain) - return false; - - FunctionDecl* decl = nullptr; - bool swapparams; - bool is_commutative = commutative(oper); - for (auto iter = chain; iter; iter = iter->next) { - auto fun = iter->as(); - if (!fun) - continue; - fun = fun->canonical(); - - bool matched = MatchOperator(oper, fun, type1, type2, numparam); - bool swapped = false; - if (!matched && is_commutative && type1 != type2 && oper) { - matched = MatchOperator(oper, fun, type2, type1, numparam); - swapped = true; - } - if (matched) { - decl = fun; - swapparams = swapped; - break; - } - } - - if (!decl) - return false; - - /* we don't want to use the redefined operator in the function that - * redefines the operator itself, otherwise the snippet below gives - * an unexpected recursion: - * fixed:operator+(fixed:a, fixed:b) - * return a + b - */ - if (decl == sc.func()) { - report(408); - } - - markusage(decl, uREAD); - - op->sym = decl; - op->oper = oper; - op->paramspassed = (oper == 0) ? 1 : numparam; - op->savepri = savepri; - op->savealt = savealt; - op->swapparams = swapparams; - return true; -} - bool checktag_string(Type* type, const value* sym1) { if (sym1->type()->isArray()) return false; @@ -577,9 +415,7 @@ bool checktag(Type* type, Type* expr_type) { * precautionary "push" of the primary register is scrapped and the constant * is read into the secondary register immediately. */ -int -commutative(int oper) -{ +bool IsOperTokenCommutative(int oper) { switch (oper) { case '+': case '*': diff --git a/compiler/expressions.h b/compiler/expressions.h index 12b13006c..29c294c4b 100644 --- a/compiler/expressions.h +++ b/compiler/expressions.h @@ -44,10 +44,8 @@ int NextExprOp(Lexer* lexer, int* opidx, int* list); struct UserOperation; bool find_userop(SemaContext& sc, int oper, Type* type1, Type* type2, int numparam, const value* lval, UserOperation* op); -bool find_userop(SemaContext& sc, int oper, int tag1, int tag2, int numparam, - const value* lval, UserOperation* op); -int commutative(int oper); +bool IsOperTokenCommutative(int oper); cell calc(cell left, int oper_tok, cell right, char* boolresult); bool IsValidIndexType(Type* type); bool matchtag(int formaltag, int actualtag, int flags); diff --git a/compiler/ir.cpp b/compiler/ir.cpp new file mode 100644 index 000000000..7ec8eb2fe --- /dev/null +++ b/compiler/ir.cpp @@ -0,0 +1,91 @@ +// 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->child()->HasSideEffects(); + } + default: + return false; + } +} + +} // namespace ir +} // namespace cc +} // namespace sp diff --git a/compiler/ir.h b/compiler/ir.h new file mode 100644 index 000000000..e996efc99 --- /dev/null +++ b/compiler/ir.h @@ -0,0 +1,676 @@ +// 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 "ast-types.h" +#include "parse-node.h" +#include "pool-objects.h" +#include "qualtype.h" + +namespace sp { +namespace cc { +namespace ir { + +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; + VarDecl* var_decl; + } pn_u; +}; + +class Value : public Node { + public: + Value(IrKind kind, Expr* expr, QualType type) + : Node(kind, expr), + type_(type) + {} + + Expr* expr() const { return pn_u.expr; } + QualType type() const { return type_; } + + bool HasSideEffects(); + + private: + QualType type_; +}; + +class StreamNode : public Node { + public: + StreamNode(IrKind kind, ParseNode* pn) + : Node(kind, pn) + {} + + StreamNode* next() const { return next_; } + void set_next(StreamNode* node) { next_ = node; } + + private: + StreamNode* next_ = nullptr; +}; + +class NodeListBuilder final { + public: + NodeListBuilder() {} + NodeListBuilder(const NodeListBuilder&) = delete; + NodeListBuilder(NodeListBuilder&&) = delete; + + explicit NodeListBuilder(NodeListBuilder** prev_loc) + : prev_(*prev_loc), + prev_loc_(prev_loc) + { + *prev_loc_ = this; + } + + ~NodeListBuilder() { + if (prev_loc_) { + assert(*prev_loc_ == this); + *prev_loc_ = prev_; + } + } + + void add(StreamNode* node) { + if (!first_) { + first_ = node; + last_ = node; + } else { + last_->set_next(node); + last_ = node; + } + } + + template + void emplace(Args&&... args) { + add(new T(std::forward(args)...)); + } + + StreamNode* Finish() { + auto tmp = first_; + first_ = nullptr; + last_ = nullptr; + return tmp; + } + + NodeListBuilder& operator =(const NodeListBuilder) = delete; + NodeListBuilder& operator =(NodeListBuilder&&) = delete; + + private: + NodeListBuilder* prev_ = nullptr; + NodeListBuilder** prev_loc_ = nullptr; + StreamNode* first_ = nullptr; + StreamNode* last_ = nullptr; +}; + +class DeclNode : public StreamNode { + public: + DeclNode(IrKind kind, Decl* decl) + : StreamNode(kind, decl) + {} +}; + +class Variable final : public DeclNode { + public: + explicit Variable(VarDecl* var) + : DeclNode(IrKind::Variable, var) + {} + + static bool is_a(Node* node) { return node->kind() == IrKind::Variable; } + + VarDecl* decl() const { return pn_u.var_decl; } +}; + +class Return final : public StreamNode { + public: + Return(ReturnStmt* stmt, Value* val) + : StreamNode(IrKind::Return, stmt), + val_(val) + {} + + Value* val() const { return val_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Return; } + + private: + Value* val_; +}; + +class Exit final : public StreamNode { + public: + Exit(ExitStmt* stmt, Value* val) + : StreamNode(IrKind::Exit, stmt) + {} + + Value* val() const { return val_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Exit; } + + private: + Value* val_; +}; + +class Assert final : public StreamNode { + public: + Assert(AssertStmt* stmt, Value* val) + : StreamNode(IrKind::Assert, stmt) + {} + + Value* val() const { return val_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Assert; } + + private: + Value* val_; +}; + +class ValueInsn final : public StreamNode { + public: + ValueInsn(ExprStmt* stmt, Value* val) + : StreamNode(IrKind::ValueInsn, stmt), + val_(val) + {} + + static bool is_a(Node* node) { return node->kind() == IrKind::ValueInsn; } + + private: + Value* val_; +}; + +class Break final : public StreamNode { + public: + explicit Break(BreakStmt* stmt) + : StreamNode(IrKind::Break, stmt) + {} + + static bool is_a(Node* node) { return node->kind() == IrKind::Break; } +}; + +class Continue final : public StreamNode { + public: + explicit Continue(ContinueStmt* stmt) + : StreamNode(IrKind::Continue, stmt) + {} + + static bool is_a(Node* node) { return node->kind() == IrKind::Continue; } +}; + +class If final : public StreamNode { + public: + If(IfStmt* stmt, Value* cond, StreamNode* on_true, StreamNode* on_false) + : StreamNode(IrKind::If, stmt), + cond_(cond), + on_true_(on_true), + on_false_(on_false) + {} + + Value* cond() const { return cond_; } + StreamNode* on_true() const { return on_true_; } + StreamNode* on_false() const { return on_false_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::If; } + + private: + Value* cond_; + StreamNode* on_true_; + StreamNode* on_false_; +}; + +class DoWhile final : public StreamNode { + public: + DoWhile(DoWhileStmt* stmt, Value* cond, StreamNode* body) + : StreamNode(IrKind::DoWhile, stmt), + cond_(cond), + body_(body) + {} + + Value* cond() const { return cond_; } + StreamNode* body() const { return body_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::DoWhile; } + + private: + Value* cond_; + StreamNode* body_; +}; + +class Delete final : public StreamNode { + public: + Delete(DeleteStmt* stmt, Value* val, MethodmapMethodDecl* dtor) + : StreamNode(IrKind::Delete, stmt), + val_(val), + dtor_(dtor) + {} + + Value* val() const { return val_; } + MethodmapMethodDecl* dtor() const { return dtor_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Delete; } + + private: + Value* val_; + MethodmapMethodDecl* dtor_; +}; + +class ForLoop final : public StreamNode { + public: + ForLoop(ForStmt* stmt, StreamNode* init, Value* cond, Value* advance, StreamNode* body) + : StreamNode(IrKind::ForLoop, stmt), + init_(init), + cond_(cond), + advance_(advance), + body_(body) + {} + + StreamNode* init() const { return init_; } + Value* cond() const { return cond_; } + Value* advance() const { return advance_; } + StreamNode* body() const { return body_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::ForLoop; } + + private: + StreamNode* init_; + Value* cond_; + Value* advance_; + StreamNode* body_; +}; + +class Switch final : public StreamNode { + public: + typedef std::pair, StreamNode*> Case; + + Switch(SwitchStmt* stmt, Value* cond, std::vector&& cases, StreamNode* default_case) + : StreamNode(IrKind::Switch, stmt), + cond_(cond), + cases_(std::move(cases)), + default_case_(default_case) + {} + + Value* cond() const { return cond_; } + const PoolArray& cases() const { return cases_; } + StreamNode* default_case() const { return default_case_; } + + static bool is_a(Node* node) { return node->kind() == IrKind::Switch; } + + private: + Value* cond_; + PoolArray cases_; + StreamNode* default_case_; +}; + +class Const final : public Value { + public: + Const(Expr* expr, QualType type, cell_t value) + : Value(IrKind::ConstVal, expr, type), + value_(value) + {} + + cell_t value() const { return value_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::ConstVal; } + + private: + cell_t value_; +}; + +class CharArrayLiteral final : public Value { + public: + CharArrayLiteral(Expr* expr, QualType type, Atom* atom) + : Value(IrKind::CharArrayLiteral, expr, type), + atom_(atom) + {} + + Atom* atom() const { return atom_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::CharArrayLiteral; } + + private: + Atom* atom_; +}; + +class UnaryOp final : public Value { + public: + UnaryOp(Expr* expr, QualType type, Value* child) + : Value(IrKind::UnaryOp, expr, type), + child_(child) + {} + + Value* child() const { return child_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::UnaryOp; } + + private: + Value* child_; +}; + +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, FunctionDecl* fun) + : Value(IrKind::FunctionRef, expr, type), + fun_(fun) + {} + + FunctionDecl* fun() const { return fun_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::FunctionRef; } + + private: + FunctionDecl* 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, FunctionDecl* target, const std::vector& values) + : Value(IrKind::CallOp, expr, type), + target_(target) + { + new (&values_) decltype(values_)(values); + } + + FunctionDecl* target() const { return target_; } + const PoolArray& values() const { return values_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::CallOp; } + + private: + FunctionDecl* target_; + PoolArray values_; +}; + +class BinaryOp final : public Value { + public: + BinaryOp(Expr* expr, QualType type, Value* left, Value* right) + : Value(IrKind::BinaryOp, expr, type), + left_(left), + right_(right) + {} + + Value* left() const { return left_; } + Value* right() const { return right_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::BinaryOp; } + + private: + Value* left_; + Value* right_; +}; + +class TernaryOp final : public Value { + public: + TernaryOp(Expr* expr, QualType type, Value* select, Value* on_true, Value* on_false) + : Value(IrKind::TernaryOp, expr, type), + select_(select), + on_true_(on_true), + on_false_(on_false) + {} + + Value* select() const { return select_; } + Value* on_true() const { return on_true_; } + Value* on_false() const { return on_false_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::TernaryOp; } + + private: + Value* select_; + Value* on_true_; + Value* on_false_; +}; + +class ThisRef final : public Value { + public: + ThisRef(ThisExpr* expr, QualType type) + : Value(IrKind::ThisRef, expr, type) + {} +}; + +class Lvalue : public Value { + public: + Lvalue(IrKind kind, Expr* expr, QualType type) + : Value(kind, expr, type) + {} + + static bool is_a(Node* op) { + return op->kind() == IrKind::VariableRef || + op->kind() == IrKind::IndexOp || + op->kind() == IrKind::TempRef || + op->kind() == IrKind::PropertyRef || + op->kind() == IrKind::FieldRef; + } +}; + +class IncDecOp final : public Value { + public: + IncDecOp(Expr* expr, QualType type, Lvalue* lval, Value* getter) + : Value(IrKind::IncDecOp, expr, type), + lval_(lval), + getter_(getter) + {} + + private: + Lvalue* lval_; + Value* getter_; +}; + +class Load final : public Value { + public: + Load(Expr* expr, QualType type, Lvalue* lval) + : Value(IrKind::Load, expr, type), + lval_(lval) + {} + + Lvalue* lval() const { return lval_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::Load; } + + private: + Lvalue* lval_; +}; + +class Store final : public Value { + public: + Store(Expr* expr, QualType type, Lvalue* loc, Value* val) + : Value(IrKind::Store, expr, type), + loc_(loc), + val_(val) + {} + + Lvalue* loc() const { return loc_; } + Value* val() const { return val_; } + + private: + Lvalue* loc_; + Value* val_; +}; + +class VariableRef final : public Lvalue { + public: + VariableRef(Expr* expr, QualType type, Variable* var) + : Lvalue(IrKind::VariableRef, expr, type), + var_(var) + {} + + Variable* var() const { return var_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::VariableRef; } + + private: + Variable* var_; +}; + +class IndexOp final : public Lvalue { + public: + IndexOp(Expr* expr, QualType type, Value* base, Value* index) + : Lvalue(IrKind::IndexOp, expr, type), + base_(base), + index_(index) + {} + + Value* base() const { return base_; } + Value* index() const { return index_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::IndexOp; } + + private: + Value* base_; + Value* index_; +}; + +class FieldRef final : public Lvalue { + public: + FieldRef(Expr* expr, QualType type, Value* base, LayoutFieldDecl* decl) + : Lvalue(IrKind::FieldRef, expr, type), + base_(base), + decl_(decl) + {} + + Value* base() const { return base_; } + LayoutFieldDecl* decl() const { return decl_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::FieldRef; } + + private: + Value* base_; + LayoutFieldDecl* decl_; +}; + +class TempRef final : public Lvalue { + public: + TempRef(Expr* expr, Value* val) + : Lvalue(IrKind::TempRef, expr, val->type()), + val_(val) + {} + + Value* val() const { return val_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::TempRef; } + + private: + Value* val_; +}; + +class PropertyRef final : public Lvalue { + public: + PropertyRef(Expr* expr, QualType type, Value* val, MethodmapPropertyDecl* decl) + : Lvalue(IrKind::PropertyRef, expr, type), + val_(val), + decl_(decl) + {} + + Value* val() const { return val_; } + MethodmapPropertyDecl* decl() const { return decl_; } + + static bool is_a(Node* op) { return op->kind() == IrKind::PropertyRef; } + + private: + Value* val_; + MethodmapPropertyDecl* decl_; +}; + +} // namespace ir +} // namespace cc +} // namespace sp diff --git a/compiler/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/parse-node.h b/compiler/parse-node.h index dc2b4efee..e3e2d61d5 100644 --- a/compiler/parse-node.h +++ b/compiler/parse-node.h @@ -927,17 +927,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/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..b4f3f8f67 100644 --- a/compiler/semantics.cpp +++ b/compiler/semantics.cpp @@ -28,6 +28,7 @@ #include "code-generator.h" #include "errors.h" #include "expressions.h" +#include "ir.h" #include "lexer.h" #include "parse-node.h" #include "sctracker.h" @@ -188,8 +189,10 @@ bool Semantics::CheckVarDecl(VarDeclBase* decl) { // be performed by the Analyze(sc) call here. // // :TODO: write flag when removing ProcessUses +#if 0 if (init && !CheckRvalue(init)) return false; +#endif auto vclass = decl->vclass(); auto init_rhs = decl->init_rhs(); @@ -328,7 +331,7 @@ static inline int GetOperToken(int token) { } } -bool Semantics::CheckExpr(Expr* expr) { +ir::Value* Semantics::CheckExpr(Expr* expr) { AutoErrorPos aep(expr->pos()); switch (expr->kind()) { case ExprKind::UnaryExpr: @@ -369,26 +372,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), @@ -493,64 +485,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 = CheckRvalue2(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 +540,109 @@ RvalueExpr::RvalueExpr(Expr* expr) } } -void -RvalueExpr::ProcessUses(SemaContext& sc) -{ +void RvalueExpr::ProcessUses(SemaContext& sc) { expr_->MarkAndProcessUses(sc); } -bool Semantics::CheckUnaryExpr(UnaryExpr* unary) { +ir::Value* Semantics::CheckUnaryExpr(UnaryExpr* unary) { AutoErrorPos aep(unary->pos()); - auto expr = unary->expr(); - if (!CheckRvalue(expr)) - return false; - if (!CheckScalarType(expr)) - return false; - - if (expr->lvalue()) - expr = unary->set_expr(new RvalueExpr(expr)); - - auto& out_val = unary->val(); - out_val = expr->val(); + ir::Value* val = CheckRvalue2(unary->expr()); + if (!val) + return nullptr; + if (!CheckScalarType(unary, val->type())) + return nullptr; // :TODO: check for invalid types UserOperation userop; switch (unary->token()) { - case '~': - if (out_val.ident == iCONSTEXPR) - out_val.set_constval(~out_val.constval()); - break; - case '!': - if (find_userop(*sc_, '!', out_val.type(), 0, 1, &out_val, &userop)) { - expr = unary->set_expr(new CallUserOpExpr(userop, expr)); - out_val = expr->val(); - unary->set_userop(); - } else if (out_val.ident == iCONSTEXPR) { - out_val.set_constval(!out_val.constval()); + case '~': { + if (val->type()->isFloat()) { + report(unary, 453) << "~" << val->type(); + return nullptr; } - out_val.set_type(types_->type_bool()); - break; - case '-': - if (out_val.ident == iCONSTEXPR && out_val.type()->isFloat()) { - float f = sp::FloatCellUnion(out_val.constval()).f32; - out_val.set_constval(sp::FloatCellUnion(-f).cell); - } else if (find_userop(*sc_, '-', out_val.type(), 0, 1, &out_val, &userop)) { - expr = unary->set_expr(new CallUserOpExpr(userop, expr)); - out_val = expr->val(); - unary->set_userop(); - } else if (out_val.ident == iCONSTEXPR) { - /* the negation of a fixed point number is just an integer negation */ - out_val.set_constval(-out_val.constval()); + + if (auto cv = val->as()) + return new ir::Const(unary, cv->type(), ~cv->value()); + + return new ir::UnaryOp(unary, val->type(), val); + } + case '!': { + auto type = types_->get_bool(); + + if (auto op = MaybeCallUserOp(unary, '!', val, nullptr)) + val = op; + + if (auto cv = val->as()) + return new ir::Const(unary, type, !cv->value()); + + return new ir::UnaryOp(unary, types_->get_bool(), val); + } + case '-': { + if (auto op = MaybeCallUserOp(unary, '~', val, nullptr)) + val = op; + + if (auto cv = val->as()) { + cell_t new_value; + if (cv->type()->isFloat()) { + float f = sp::FloatCellUnion(cv->value()).f32; + new_value = sp::FloatCellUnion(-f).cell; + } else { + new_value = -cv->value(); + } + return new ir::Const(unary, val->type(), new_value); } - break; + return new ir::UnaryOp(unary, val->type(), val); + } default: assert(false); + return nullptr; } - - if (out_val.ident != iCONSTEXPR) - out_val.ident = iEXPRESSION; - return true; } -void -UnaryExpr::ProcessUses(SemaContext& sc) -{ +void UnaryExpr::ProcessUses(SemaContext& sc) { expr_->MarkAndProcessUses(sc); } -bool Semantics::CheckIncDecExpr(IncDecExpr* incdec) { +ir::Value* Semantics::CheckIncDecExpr(IncDecExpr* incdec) { AutoErrorPos aep(incdec->pos()); - auto expr = incdec->expr(); - if (!CheckExpr(expr)) - return false; - if (!CheckScalarType(expr)) - return false; - if (!expr->lvalue()) { + auto val = CheckExpr(incdec->expr()); + if (!val) + return nullptr; + if (!CheckScalarType(incdec, val->type())) + return nullptr; + auto lval = val->as(); + if (!lval) { report(incdec, 22); - return false; + return nullptr; } - const auto& expr_val = expr->val(); - if (expr_val.ident != iACCESSOR) { - if (expr_val.sym && expr_val.sym->is_const()) { - report(incdec, 22); /* assignment to const argument */ - return false; - } - } else { - if (!expr_val.accessor()->setter()) { - report(incdec, 152) << expr_val.accessor()->name(); - return false; + if (lval->type().is_const()) { + report(incdec, 22); + return nullptr; + } + + if (auto prop = lval->as()) { + auto decl = prop->decl(); + if (!decl->setter()) { + report(incdec, 152) << decl->name(); + return nullptr; } - if (!expr_val.accessor()->getter()) { - report(incdec, 149) << expr_val.accessor()->name(); - return false; + if (!decl->getter()) { + report(incdec, 149) << decl->name(); + return nullptr; } - markusage(expr_val.accessor()->getter(), uREAD); - markusage(expr_val.accessor()->setter(), uREAD); + markusage(decl->getter(), uREAD); + markusage(decl->setter(), uREAD); } - Type* type = expr_val.type(); - if (type->isReference()) - type = type->inner(); + ir::Value* getter = BuildRvalue(incdec, lval); + if (auto userop = MaybeCallUserOp(incdec, incdec->token(), getter, nullptr)) + getter = userop; - find_userop(*sc_, incdec->token(), type, 0, 1, &expr_val, &incdec->userop()); - - // :TODO: more type checks - auto& val = incdec->val(); - val.ident = iEXPRESSION; - val.set_type(type); - return true; + return new ir::IncDecOp(incdec, getter->type(), lval, getter); } void @@ -705,180 +668,135 @@ BinaryExpr::BinaryExpr(const token_pos_t& pos, int token, Expr* left, Expr* righ oper_tok_ = GetOperToken(token_); } -bool Semantics::CheckBinaryExpr(BinaryExpr* expr) { - AutoErrorPos aep(expr->pos()); +static inline bool CanConstFoldType(QualType type) { + return type->isInt() || + type->isChar() || + type->isBool() || + type->isEnum(); +} - auto left = expr->left(); - auto right = expr->right(); - if (!CheckExpr(left) || !CheckRvalue(right)) - return false; +ir::Value* Semantics::CheckBinaryExpr(BinaryExpr* expr) { + AutoErrorPos aep(expr->pos()); int token = expr->token(); - if (token != '=') { - if (!CheckScalarType(left)) - return false; - if (!CheckScalarType(right)) - return false; - } - - if (IsAssignOp(token)) { - // Mark the left-hand side as written as soon as we can. - if (Decl* sym = left->val().sym) { - markusage(sym, uWRITTEN); - // If it's an outparam, also mark it as read. - if (sym->vclass() == sARGUMENT && - (sym->type()->isReference() || sym->type()->isArray())) - { - markusage(sym, uREAD); - } - } else if (auto* accessor = left->val().accessor()) { - if (!accessor->setter()) { - report(expr, 152) << accessor->name(); - return false; - } - markusage(accessor->setter(), uREAD); - if (accessor->getter() && token != '=') - markusage(accessor->getter(), uREAD); - } - - if (!CheckAssignmentLHS(expr)) - return false; - if (token != '=' && !CheckRvalue(left->pos(), left->val())) - return false; - } else if (left->lvalue()) { - if (!CheckRvalue(left->pos(), left->val())) - return false; - left = expr->set_left(new RvalueExpr(left)); - } - - // RHS is always loaded. Note we do this after validating the left-hand side, - // so ValidateAssignment has an original view of RHS. - if (right->lvalue()) - right = expr->set_right(new RvalueExpr(right)); - - const auto& left_val = left->val(); - const auto& right_val = right->val(); - - auto oper_tok = expr->oper(); - if (oper_tok) { - assert(token != '='); + auto left = CheckExpr(expr->left()); + if (!left) + return nullptr; + auto right = CheckRvalue2(expr->right()); + if (!right) + return nullptr; - if (left_val.type()->isArray()) { - const char* ptr = (left_val.sym != nullptr) ? left_val.sym->name()->chars() : "-unknown-"; - report(expr, 33) << ptr; /* array must be indexed */ - return false; + auto lval = left->as(); + if (IsAssignOp(token)) { + if (!lval) { + report(expr, 455); + return nullptr; } - if (right_val.type()->isArray()) { - const char* ptr = (right_val.sym != nullptr) ? right_val.sym->name()->chars() : "-unknown-"; - report(expr, 33) << ptr; /* array must be indexed */ - return false; + if (!CheckAssignmentLHS(expr, lval)) + return nullptr; + if (token != '=') { + left = BuildRvalue(expr->left(), lval); + MarkUsage(lval, uREAD); } - /* ??? ^^^ should do same kind of error checking with functions */ + MarkUsage(lval, uWRITTEN); + } else if (lval) { + left = BuildRvalue(expr->left(), lval); } - // The assignment operator is overloaded separately. - if (IsAssignOp(token)) { - if (!CheckAssignmentRHS(expr)) - return false; + if (token != '=') { + if (!CheckScalarType(expr->left(), left->type())) + return nullptr; + if (!CheckScalarType(expr->right(), right->type())) + return nullptr; } - auto& val = expr->val(); - val.ident = iEXPRESSION; - val.set_type(left_val.type()); - - auto& assignop = expr->assignop(); - if (assignop.sym) - val.set_type(assignop.sym->type()); - - if (oper_tok) { - auto& userop = expr->userop(); - if (find_userop(*sc_, oper_tok, left_val.type(), right_val.type(), 2, nullptr, &userop)) { - val.set_type(userop.sym->type()); - } else if (left_val.ident == iCONSTEXPR && right_val.ident == iCONSTEXPR) { - char boolresult = FALSE; - TypeChecker::DoCoerce(expr->pos(), left_val.type(), right_val.type()); - val.ident = iCONSTEXPR; - val.set_constval(calc(left_val.constval(), oper_tok, right_val.constval(), - &boolresult)); - } else { - // For the purposes of tag matching, we consider the order to be irrelevant. - Type* left_type = left_val.type(); - if (left_type->isReference()) - left_type = left_type->inner(); + ir::Value* out = nullptr; + if (token != '=') + out = MaybeCallUserOp(expr, expr->token(), left, right); - Type* right_type = right_val.type(); - if (right_type->isReference()) - right_type = right_type->inner(); + if (!out) { + TypeChecker::DoCoerce(expr->pos(), *left->type(), *right->type(), TypeChecker::Commutative); - TypeChecker::DoCoerce(expr->pos(), left_type, right_type, TypeChecker::Commutative); + auto left_cv = left->as(); + auto right_cv = right->as(); + if (left_cv && CanConstFoldType(left_cv->type()) && + right_cv && CanConstFoldType(right_cv->type())) + { + char is_bool = false; + cell result = calc(left_cv->value(), token, right_cv->value(), &is_bool); + auto result_type = is_bool ? types_->get_bool() : left_cv->type(); + out = new ir::Const(expr, result_type, result); } + } + if (!out) { + QualType type; if (IsChainedOp(token) || token == tlEQ || token == tlNE) - val.set_type(types_->type_bool()); + type = types_->get_bool(); + else + type = left->type(); + out = new ir::BinaryOp(expr, type, left, right); } - return true; + if (IsAssignOp(token)) + out = new ir::Store(expr, lval->type(), lval, out); + + return out; } -bool Semantics::CheckAssignmentLHS(BinaryExpr* expr) { - auto left = expr->left(); - int left_ident = left->val().ident; - if (left_ident == iARRAYCHAR) { +bool Semantics::CheckAssignmentLHS(BinaryExpr* expr, ir::Lvalue* lval) { + if (lval->type()->isCharArray()) { // This is a special case, assigned to a packed character in a cell // is permitted. return true; } - int oper_tok = expr->oper(); - if (auto left_array = left->val().type()->as()) { - // array assignment is permitted too (with restrictions) - if (oper_tok) { - report(expr, 23); - return false; - } - + // :TODO: is this needed? TypeChecker should cover it. + if (auto left_array = lval->type()->as()) { for (auto iter = left_array; iter; iter = iter->inner()->as()) { if (!iter->size()) { - report(left, 46); + report(expr->left(), 46); return false; } } return true; } - if (!left->lvalue()) { - report(expr, 22); - return false; - } - - const auto& left_val = left->val(); // may not change "constant" parameters - if (!expr->initializer() && left_val.sym && left_val.sym->is_const()) { + if (lval->type().is_const()) { report(expr, 22); return false; } return true; } -bool Semantics::CheckAssignmentRHS(BinaryExpr* expr) { - auto left = expr->left(); - auto right = expr->right(); - const auto& left_val = left->val(); - const auto& right_val = right->val(); +static inline void CheckSelfAssignment(BinaryExpr* expr, ir::Lvalue* lval, ir::Value* rval) { + auto left_var = lval->as(); + if (!left_var) + return; - if (left_val.ident == iVARIABLE) { - const auto& right_val = right->val(); - if (right_val.ident == iVARIABLE && right_val.sym == left_val.sym && !expr->oper()) - report(expr, 226) << left_val.sym->name(); // self-assignment - } + auto load = rval->as(); + if (!load) + return; + + auto right_var = load->lval()->as(); + if (!right_var) + return; + + if (left_var->var() == right_var->var()) + report(expr, 226) << left_var->var()->decl()->name(); +} + +bool Semantics::CheckAssignmentRHS(BinaryExpr* expr, ir::Lvalue* lval, ir::Value* rval) { + CheckSelfAssignment(expr, lval, rval); - if (auto left_array = left_val.type()->as()) { - TypeChecker tc(expr, left_val.type(), right_val.type(), TypeChecker::Assignment); + if (auto left_array = lval->type()->as()) { + TypeChecker tc(expr, lval->type(), rval->type(), TypeChecker::Assignment); if (!tc.Coerce()) return false; - auto right_array = right_val.type()->to(); + auto right_array = rval->type()->to(); if (right_array->inner()->isArray()) { report(expr, 23); return false; @@ -891,25 +809,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 +838,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,6 +850,7 @@ bool Semantics::CheckAssignmentRHS(BinaryExpr* expr) { TypeChecker::DoCoerce(expr->pos(), left_val.type(), right_val.type()); } } +#endif return true; } @@ -1004,125 +926,62 @@ 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, left, right); } -bool Semantics::CheckChainedCompareExpr(ChainedCompareExpr* chain) { - auto first = chain->first(); - if (!CheckRvalue(first)) - return false; - if (first->lvalue()) - first = chain->set_first(new RvalueExpr(first)); - - for (auto& op : chain->ops()) { - if (!CheckRvalue(op.expr)) - return false; - if (op.expr->lvalue()) - op.expr = new RvalueExpr(op.expr); - } - - Expr* left = first; - bool all_const = (left->val().ident == iCONSTEXPR); - bool constval = true; - - auto& val = chain->val(); - val.ident = iEXPRESSION; - val.set_type(types_->type_bool()); - - for (auto& op : chain->ops()) { - Expr* right = op.expr; - const auto& left_val = left->val(); - const auto& right_val = right->val(); +ir::Value* Semantics::CheckChainedCompareExpr(ChainedCompareExpr* chain) { + auto first = CheckRvalue2(chain->first()); + if (!first) + return nullptr; - if (left_val.type()->isArray()) { - const char* ptr = (left_val.sym != nullptr) ? left_val.sym->name()->chars() : "-unknown-"; - report(left, 33) << ptr; /* array must be indexed */ - return false; - } - if (right_val.type()->isArray()) { - const char* ptr = (right_val.sym != nullptr) ? right_val.sym->name()->chars() : "-unknown-"; - report(right, 33) << ptr; /* array must be indexed */ - return false; - } + ir::Value* left = first; + ir::Value* out = nullptr; + for (const auto& chain_op : chain->ops()) { + auto right = CheckRvalue2(chain_op.expr); + if (!right) + return nullptr; - 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(), left, right); } + if (!out) + out = op; + else + out = new ir::BinaryOp(chain, types_->get_bool(), out, op); left = right; } - if (all_const) - val.set_constval(constval ? 1 : 0); - return true; + return out; } void @@ -1133,47 +992,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 = CheckRvalue2(expr->first()); + if (!first) + return nullptr; + auto second = CheckRvalue2(expr->second()); + if (!second) + return nullptr; + auto third = CheckRvalue2(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; + return new ir::TernaryOp(expr, type, first, second, third); } bool @@ -1206,26 +1054,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 +1077,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 +1126,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 +1170,52 @@ bool Semantics::CheckSymbolExpr(SymbolExpr* expr, bool allow_types) { } switch (decl->ident()) { - case iVARIABLE: - expr->set_lvalue(true); - break; + case iVARIABLE: { + auto var = decl->as(); + assert(var); + + auto it = sc_->local_vars().find(var); + assert(it != sc_->local_vars().end()); + + return new ir::VariableRef(expr, QualType(type), it->second); + } case iFUNCTN: - // Not an l-value. - break; + return new ir::FunctionRef(expr, QualType(type), decl->to()); 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]); + + std::vector values; + for (size_t i = 0; i < comma->exprs().size(); i++) { + auto expr = comma->exprs().at(i); - Expr* last = comma->exprs().back(); - if (comma->exprs().size() > 1 && last->lvalue()) { - last = new RvalueExpr(last); - comma->exprs().back() = last; + // Don't bother converting ignored results to rvalues. + ir::Value* val; + if (i == comma->exprs().size() - 1) + val = CheckRvalue2(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 +1224,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 +1242,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 = CheckRvalue2(expr); + if (!val) + return nullptr; - const auto& val = expr->val(); - if (val.ident != iCONSTEXPR) { + values.emplace_back(val); + + auto cv = val->as(); + if (!cv) { report(expr, 8); - return false; + return nullptr; } if (!last_type) { - last_type = val.type(); + last_type = val->type(); continue; } - TypeChecker tc(array, last_type, val.type(), TypeChecker::Generic); + TypeChecker tc(array, last_type, val->type(), TypeChecker::Generic); if (!tc.Check()) - return false; + return nullptr; } - auto& val = array->val(); - val.ident = iEXPRESSION; - val.set_type(types_->defineArray(last_type, (int)array->exprs().size())); - return true; + auto type = types_->defineArray(last_type.ptr(), (int)values.size()); + return new ir::Array(array, QualType(type), values); } -bool Semantics::CheckIndexExpr(IndexExpr* expr) { +ir::Value* Semantics::CheckIndexExpr(IndexExpr* expr) { AutoErrorPos aep(expr->pos()); - auto base = expr->base(); - auto index = expr->index(); - if (!CheckRvalue(base)) - return false; - if (base->lvalue() && base->val().ident == iACCESSOR) - base = expr->set_base(new RvalueExpr(base)); + auto base = CheckRvalue2(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 = CheckRvalue2(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 +1318,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), expr->text()); } -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 = CheckRvalue2(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; +#if 0 + // :TODO: proper type +#endif markusage(method, uREAD); - return true; + return new ir::FunctionRef(expr, QualType(method->return_type()), method); } - 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; + // :TODO: real fun type + return new ir::FunctionRef(expr, QualType(method->return_type()), method); } void @@ -1740,149 +1523,116 @@ FunctionDecl* Semantics::BindNewTarget(Expr* target) { 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; + // :TODO: real type + return new ir::FunctionRef(expr, QualType(fun->return_type()), 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; + return new ir::Const(child, types_->get_int(), 1); - case iARRAYCHAR: - report(expr, 252); - val.set_constval(1); - return true; - - 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,7 +1659,7 @@ 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 @@ -1920,7 +1670,7 @@ bool Semantics::CheckCallExpr(CallExpr* call) { else fun = BindCallTarget(call, call->target()); if (!fun) - return false; + return nullptr; assert(fun->canonical() == fun); @@ -1931,7 +1681,7 @@ bool Semantics::CheckCallExpr(CallExpr* call) { // the function. if (fun->is_analyzing() || !CheckFunctionDecl(fun)) { report(call, 411); - return false; + return nullptr; } } @@ -1955,11 +1705,11 @@ bool Semantics::CheckCallExpr(CallExpr* call) { 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 +1719,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 = fun->FindNamedArg(named->name()); if (pos < 0) { - report(call, 17) << named->name; + report(call, 17) << named->name(); break; } argpos = pos; @@ -1979,28 +1729,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 +1762,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,10 +1774,12 @@ 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; } + assert(false); +#if 0 Expr* expr = ps.argv[argidx]; if (expr->as() && !IsReferenceType(iVARIABLE, arg->type())) { UserOperation userop; @@ -2037,24 +1789,19 @@ bool Semantics::CheckCallExpr(CallExpr* call) { ps.argv[argidx] = new CallUserOpExpr(userop, expr); } } +#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(fun->return_type()), fun, ps.argv); } -Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, - ParamState* ps, unsigned int pos) +ir::Value* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, + ParamState* ps, unsigned int pos) { while (pos >= ps->argv.size()) ps->argv.push_back(nullptr); +#if 0 unsigned int visual_pos = call->implicit_this() ? pos : pos + 1; if (!param || param->as()) { @@ -2082,9 +1829,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 +1842,29 @@ Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, bool handling_this = call->implicit_this() && (pos == 0); if (param->val().ident == iACCESSOR) { - if (!CheckRvalue(param->pos(), param->val())) +#if 0 + if (val = CheckRvalue(val); !val) return nullptr; - param = new RvalueExpr(param); +#endif + assert(false); } - const auto* val = ¶m->val(); - bool lvalue = param->lvalue(); if (arg->type_info().is_varargs) { assert(!handling_this); // Always pass by reference. - if (val->ident == iVARIABLE) { - if (val->sym->is_const() && !arg->type_info().is_const) { - // Treat a "const" variable passed to a function with a - // non-const "variable argument list" as a constant here. - if (!lvalue) { - report(param, 22); // need lvalue - return nullptr; - } - NeedsHeapAlloc(param); - } else if (!lvalue) { - NeedsHeapAlloc(param); - } - } else if (val->ident == iCONSTEXPR || val->ident == iEXPRESSION) { + if (!val->type()->isReferenceType() && !val->as()) { NeedsHeapAlloc(param); + val = new ir::TempRef(param, val); } - if (!checktag_string(arg->type(), val) && !checktag(arg->type(), val->type())) +#if 0 + if (!checktag_string(arg->type(), val) && !checktag(arg->type(), val->type().ptr())) report(param, 213) << arg->type() << val->type(); +#endif } else if (arg->type()->isReference()) { assert(!handling_this); +#if 0 if (!lvalue || val->ident == iARRAYCHAR) { report(param, 35) << visual_pos; // argument type mismatch return nullptr; @@ -2133,7 +1874,9 @@ Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, return nullptr; } checktag(arg->type()->inner(), val->type()); +#endif } else if (arg->type()->isArray()) { +#if 0 // If the input type is an index into an array, create an implicit // array type to represent the slice. Type* type = val->type(); @@ -2148,12 +1891,12 @@ Expr* Semantics::CheckArgument(CallExpr* call, ArgDecl* arg, Expr* param, report(param, 35) << visual_pos; // argument type mismatch return nullptr; } +#endif } else { - if (lvalue) { - param = new RvalueExpr(param); - val = ¶m->val(); - } + if (auto lval = val->as(); lval) + val = new ir::Load(param, val->type(), lval); +#if 0 // Do not allow user operators to transform |this|. UserOperation userop; if (!handling_this && @@ -2166,8 +1909,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 +1929,46 @@ CallExpr::MarkUsed(SemaContext& sc) } bool Semantics::CheckStaticAssertStmt(StaticAssertStmt* stmt) { - auto expr = stmt->expr(); - if (!CheckExpr(expr)) + auto val = CheckRvalue2(stmt->expr()); + if (!val) return false; // :TODO: insert coercion to bool. - cell value; - Type* type; - if (!expr->EvalConst(&value, &type)) { - report(expr, 8); + auto cv = val->as(); + if (!cv) { + report(stmt->expr(), 8); return false; } - if (value) + if (cv->value()) return true; std::string message; if (stmt->text()) message += ": " + std::string(stmt->text()->chars(), stmt->text()->length()); - report(expr, 70) << message; + report(stmt, 70) << message; return false; } -bool Semantics::CheckNewArrayExpr(NewArrayExpr* expr) { +ir::Value* Semantics::CheckNewArrayExpr(NewArrayExpr* expr) { // We can't handle random refarrays floating around yet, so forbid this. report(expr, 142); - return false; + return nullptr; } bool Semantics::CheckNewArrayExprForArrayInitializer(NewArrayExpr* na) { +#if 0 if (na->analyzed()) return na->analysis_result(); na->set_analysis_result(false); - auto& val = na->val(); - val.ident = iEXPRESSION; - PoolList dims; for (auto& expr : na->exprs()) { - if (!CheckRvalue(expr)) + auto val = CheckExpr(expr); + if (!val) return false; - if (expr->lvalue()) - expr = new RvalueExpr(expr); const auto& v = expr->val(); if (IsLegacyEnumType(sc_->scope(), v.type())) { @@ -2239,7 +1979,9 @@ bool Semantics::CheckNewArrayExprForArrayInitializer(NewArrayExpr* na) { report(expr, 77) << v.type(); return false; } - if (v.ident == iCONSTEXPR && v.constval() <= 0) { + + auto cv = val->as(); + if (cv && cv->value() <= 0) { report(expr, 9); return false; } @@ -2248,6 +1990,8 @@ bool Semantics::CheckNewArrayExprForArrayInitializer(NewArrayExpr* na) { assert(na->type()->isArray()); na->set_analysis_result(true); +#endif + assert(false); return true; } @@ -2259,22 +2003,32 @@ NewArrayExpr::ProcessUses(SemaContext& sc) } bool Semantics::CheckIfStmt(IfStmt* stmt) { - if (Expr* expr = AnalyzeForTest(stmt->cond())) - stmt->set_cond(expr); + ir::Value* cond = AnalyzeForTest(stmt->cond()); + if (!cond) + return false; // Note: unlike loop conditions, we don't factor in constexprs here, it's // too much work and way less common than constant loop conditions. + ir::StreamNode* on_true = nullptr; + ir::StreamNode* on_false = nullptr; ke::Maybe always_returns; { AutoCollectSemaFlow flow(*sc_, &always_returns); + + ir::NodeListBuilder builder(&ir_); if (!CheckStmt(stmt->on_true(), STMT_OWNS_HEAP)) return false; + on_true = builder.Finish(); } { AutoCollectSemaFlow flow(*sc_, &always_returns); - if (stmt->on_false() && !CheckStmt(stmt->on_false(), STMT_OWNS_HEAP)) - return false; + if (stmt->on_false()) { + ir::NodeListBuilder builder(&ir_); + if (!CheckStmt(stmt->on_false(), STMT_OWNS_HEAP)) + return false; + on_false = builder.Finish(); + } } if (stmt->on_false()) { @@ -2294,15 +2048,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 = CheckRvalue2(stmt->expr()); + if (!val) return false; - if (!expr->HasSideEffects()) - report(expr, 215); + + if (!val->HasSideEffects()) + report(stmt, 215); + + ir_->emplace(stmt, val); return true; } @@ -2419,11 +2178,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 +2198,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 +2215,10 @@ bool Semantics::CheckReturnStmt(ReturnStmt* stmt) { } } - if (!CheckRvalue(expr)) + auto val = CheckRvalue2(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 +2228,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 +2284,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 +2314,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 = CheckRvalue2(stmt->expr()); + if (!val) return false; - if (expr->lvalue()) - expr = stmt->set_expr(new RvalueExpr(expr)); - if (!IsValueKind(expr->val().ident)) { - report(expr, 106); + AutoErrorPos aep(stmt->pos()); + if (!TypeChecker::DoCoerce(stmt->expr()->pos(), types_->get_int(), val->type())) return false; - } - - AutoErrorPos aep(expr->pos()); - if (!TypeChecker::DoCoerce(types_->type_int(), expr)) - return false; + ir_->emplace(stmt, val); return true; } bool Semantics::CheckDoWhileStmt(DoWhileStmt* stmt) { + ir::Value* cond; { ke::SaveAndSet restore_heap(&pending_heap_allocation_, false); - if (Expr* expr = AnalyzeForTest(stmt->cond())) { - stmt->set_cond(expr); - AssignHeapOwnership(expr); - } + if ((cond = AnalyzeForTest(stmt->cond())) == nullptr) + return false; +#if 0 + AssignHeapOwnership(stmt->cond()); +#endif } - auto cond = stmt->cond(); - ke::Maybe constval; - if (cond->val().ident == iCONSTEXPR) - constval.init(cond->val().constval()); + if (auto cv = cond->as()) + constval.init(cv->value()); + ir::StreamNode* body; bool has_break = false; bool has_return = false; ke::Maybe always_returns; @@ -2631,8 +2362,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 +2387,46 @@ bool Semantics::CheckDoWhileStmt(DoWhileStmt* stmt) { } // :TODO: endless loop warning? + ir_->emplace(stmt, cond, body); return true; } bool Semantics::CheckForStmt(ForStmt* stmt) { bool ok = true; - if (stmt->init() && !CheckStmt(stmt->init())) - ok = false; - auto cond = stmt->cond(); - if (cond) { - if (Expr* expr = AnalyzeForTest(cond)) - cond = stmt->set_cond(expr); + ir::StreamNode* init = nullptr; + if (stmt->init()) { + ir::NodeListBuilder builder(&ir_); + if (CheckStmt(stmt->init())) + init = builder.Finish(); else ok = false; } + + ir::Value* cond = nullptr; + if (stmt->cond()) { + if ((cond = AnalyzeForTest(stmt->cond())) == nullptr) + ok = false; + } + + ir::Value* advance = nullptr; if (stmt->advance()) { ke::SaveAndSet restore(&pending_heap_allocation_, false); - if (CheckRvalue(stmt->advance())) + if ((advance = CheckExpr(stmt->advance())) != nullptr) { + // :TODO: check if has effect +#if 0 AssignHeapOwnership(stmt->advance()); - else +#endif + } else { ok = false; + } } ke::Maybe constval; - if (cond && cond->val().ident == iCONSTEXPR) - constval.init(cond->val().constval()); + if (auto cv = cond->as()) + constval.init(cv->value()); + ir::StreamNode* body_ir; bool has_break = false; bool has_return = false; ke::Maybe always_returns; @@ -2690,7 +2436,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 +2471,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 = CheckRvalue2(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 +2501,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 = CheckRvalue2(expr); + if (!val) + return false; - cell value; - Type* type; - if (!expr->EvalConst(&value, &type)) { + auto cv = val->as(); + if (!cv) { report(expr, 8); - continue; - } - if (tag_ok) { - AutoErrorPos aep(expr->pos()); - TypeChecker::DoCoerce(expr->pos(), v.type(), type); + return false; } - if (!case_values.count(value)) - case_values.emplace(value); + AutoErrorPos aep(expr->pos()); + TypeChecker::DoCoerce(expr->pos(), cond->type(), cv->type()); + + if (!case_values.count(cv->value())) + case_values.emplace(cv->value()); else - report(expr, 40) << value; + report(expr, 40) << cv->value(); + + entry_values[i] = cv->value(); } AutoCollectSemaFlow flow(*sc_, &always_returns); - if (CheckStmt(case_entry.second)) - update_flow(case_entry.second->flow_type()); + + ir::NodeListBuilder builder(&ir_); + if (!CheckStmt(case_entry.second)) + return false; + out_cases.emplace_back(std::move(entry_values), builder.Finish()); + + update_flow(case_entry.second->flow_type()); } + ir::StreamNode* default_case = nullptr; if (stmt->default_case()) { AutoCollectSemaFlow flow(*sc_, &always_returns); - if (CheckStmt(stmt->default_case())) - update_flow(stmt->default_case()->flow_type()); + + ir::NodeListBuilder builder(&ir_); + if (!CheckStmt(stmt->default_case())) + return false; + default_case = builder.Finish(); + + update_flow(stmt->default_case()->flow_type()); } else { always_returns.init(false); update_flow(Flow_None); @@ -2790,7 +2558,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; } @@ -3234,26 +3002,176 @@ void Semantics::DeduceMaybeUsed() { } } -bool Semantics::CheckRvalue(Expr* expr) { - if (!CheckExpr(expr)) - return false; - return CheckRvalue(expr->pos(), expr->val()); +QualType Semantics::BuildRvalueType(QualType type) { + if (type->isReference()) + return QualType(type->inner()); + return type; +} + +ir::Value* Semantics::BuildRvalue(Expr* expr, ir::Lvalue* lval) { + QualType type = lval->type(); + if (lval->as()) + type = BuildRvalueType(type); + + assert(!type->isReference()); + return new ir::Load(expr, type, lval); } -bool Semantics::CheckRvalue(const token_pos_t& pos, const value& val) { - if (auto accessor = val.accessor()) { - if (!accessor->getter()) { - report(pos, 149) << accessor->name(); +ir::Value* Semantics::CheckRvalue2(Expr* expr) { + auto val = CheckExpr(expr); + if (!val) + return nullptr; + + if (auto lval = val->as()) + return BuildRvalue(expr, lval); + + return val; +} + +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; + } + default: + break; + } +} + } // namespace cc } // namespace sp diff --git a/compiler/semantics.h b/compiler/semantics.h index d5518f100..d3849afe0 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" @@ -118,6 +119,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_; @@ -136,6 +138,7 @@ class SemaContext bool preprocessing_ = false; SemaContext* cc_prev_sc_ = nullptr; std::unordered_set static_scopes_; + std::unordered_map local_vars_; }; class Semantics final @@ -172,74 +175,78 @@ class Semantics final bool CheckFunctionDecl(FunctionDecl* info); bool CheckFunctionDeclImpl(FunctionDecl* info); void CheckFunctionReturnUsage(FunctionDecl* info); - bool CheckPragmaUnusedStmt(PragmaUnusedStmt* stmt); - bool CheckSwitchStmt(SwitchStmt* stmt); - bool CheckForStmt(ForStmt* stmt); - bool CheckDoWhileStmt(DoWhileStmt* stmt); + bool CheckVarDecl(VarDeclBase* decl); + bool CheckPstructDecl(VarDeclBase* decl); + bool CheckPstructArg(VarDeclBase* decl, PstructDecl* ps, StructInitFieldExpr* field, + std::vector* visited); + + // Ported to IR. + bool CheckAssertStmt(AssertStmt* stmt); bool CheckBreakStmt(BreakStmt* stmt); + bool CheckCompoundReturnStmt(ReturnStmt* stmt); bool CheckContinueStmt(ContinueStmt* stmt); - bool CheckExitStmt(ExitStmt* stmt); bool CheckDeleteStmt(DeleteStmt* stmt); - bool CheckAssertStmt(AssertStmt* stmt); - bool CheckStaticAssertStmt(StaticAssertStmt* stmt); - bool CheckReturnStmt(ReturnStmt* stmt); - bool CheckCompoundReturnStmt(ReturnStmt* stmt); + bool CheckDoWhileStmt(DoWhileStmt* stmt); + bool CheckExitStmt(ExitStmt* stmt); bool CheckExprStmt(ExprStmt* stmt); + bool CheckForStmt(ForStmt* stmt); bool CheckIfStmt(IfStmt* stmt); - bool CheckConstDecl(ConstDecl* decl); - bool CheckVarDecl(VarDeclBase* decl); - bool CheckConstDecl(VarDecl* decl); - bool CheckPstructDecl(VarDeclBase* decl); - bool CheckPstructArg(VarDeclBase* decl, PstructDecl* ps, StructInitFieldExpr* field, - std::vector* visited); + bool CheckPragmaUnusedStmt(PragmaUnusedStmt* stmt); + bool CheckReturnStmt(ReturnStmt* stmt); + bool CheckSwitchStmt(SwitchStmt* stmt); + bool CheckStaticAssertStmt(StaticAssertStmt* stmt); // Expressions. - bool CheckExpr(Expr* expr); - bool CheckNewArrayExpr(NewArrayExpr* expr); - bool CheckArrayExpr(ArrayExpr* expr); - bool CheckStringExpr(StringExpr* expr); - bool CheckTaggedValueExpr(TaggedValueExpr* expr); - bool CheckNullExpr(NullExpr* expr); - bool CheckThisExpr(ThisExpr* expr); - bool CheckCommaExpr(CommaExpr* expr); - bool CheckIndexExpr(IndexExpr* expr); - bool CheckCallExpr(CallExpr* expr); - bool CheckSymbolExpr(SymbolExpr* expr, bool allow_types); - bool CheckSizeofExpr(SizeofExpr* expr); - bool CheckCastExpr(CastExpr* expr); - bool CheckIncDecExpr(IncDecExpr* expr); - bool CheckTernaryExpr(TernaryExpr* expr); - bool CheckChainedCompareExpr(ChainedCompareExpr* expr); - bool CheckLogicalExpr(LogicalExpr* expr); - bool CheckBinaryExpr(BinaryExpr* expr); - bool CheckUnaryExpr(UnaryExpr* expr); - bool CheckFieldAccessExpr(FieldAccessExpr* expr, bool from_call); - bool CheckStaticFieldAccessExpr(FieldAccessExpr* expr); - bool CheckEnumStructFieldAccessExpr(FieldAccessExpr* expr, Type* type, EnumStructDecl* root, - bool from_call); - bool CheckRvalue(Expr* expr); - bool CheckRvalue(const token_pos_t& pos, const value& val); - - bool CheckAssignmentLHS(BinaryExpr* expr); - bool CheckAssignmentRHS(BinaryExpr* expr); + ir::Value* CheckExpr(Expr* expr); + ir::Value* CheckNewArrayExpr(NewArrayExpr* expr); + ir::Value* CheckArrayExpr(ArrayExpr* expr); + ir::Value* CheckStringExpr(StringExpr* expr); + ir::Value* CheckTaggedValueExpr(TaggedValueExpr* expr); + ir::Value* CheckNullExpr(NullExpr* expr); + ir::Value* CheckThisExpr(ThisExpr* expr); + ir::Value* CheckCommaExpr(CommaExpr* expr); + ir::Value* CheckIndexExpr(IndexExpr* expr); + ir::Value* CheckCallExpr(CallExpr* expr); + ir::Value* CheckSymbolExpr(SymbolExpr* expr, bool allow_types); + ir::Value* CheckSizeofExpr(SizeofExpr* expr); + ir::Value* CheckCastExpr(CastExpr* expr); + ir::Value* CheckIncDecExpr(IncDecExpr* expr); + ir::Value* CheckTernaryExpr(TernaryExpr* expr); + ir::Value* CheckChainedCompareExpr(ChainedCompareExpr* expr); + ir::Value* CheckLogicalExpr(LogicalExpr* expr); + ir::Value* CheckBinaryExpr(BinaryExpr* expr); + ir::Value* CheckUnaryExpr(UnaryExpr* expr); + ir::Value* CheckFieldAccessExpr(FieldAccessExpr* expr, bool from_call); + ir::Value* CheckStaticFieldAccessExpr(FieldAccessExpr* expr); + ir::Value* CheckEnumStructFieldAccessExpr(FieldAccessExpr* expr, ir::Value* base, + EnumStructDecl* root, bool from_call); + ir::Value* CheckRvalue2(Expr* expr); + ir::Value* CheckRvalue(ir::Value* val); + ir::Value* BuildRvalue(Expr* expr, ir::Lvalue* val); + QualType BuildRvalueType(QualType type); + void MarkUsage(ir::Value* val, uint8_t usage); + + bool CheckAssignmentLHS(BinaryExpr* expr, ir::Lvalue* lval); + bool CheckAssignmentRHS(BinaryExpr* expr, ir::Lvalue* lval, ir::Value* rval); bool AddImplicitDynamicInitializer(VarDeclBase* decl); struct ParamState { - std::vector argv; + std::vector argv; }; bool CheckArrayDeclaration(VarDeclBase* decl); bool CheckNewArrayExprForArrayInitializer(NewArrayExpr* expr); - Expr* CheckArgument(CallExpr* call, ArgDecl* arg, Expr* expr, - ParamState* ps, unsigned int argpos); - bool CheckWrappedExpr(Expr* outer, Expr* inner); + ir::Value* CheckArgument(CallExpr* call, ArgDecl* arg, Expr* expr, ParamState* ps, + unsigned int argpos); FunctionDecl* BindNewTarget(Expr* target); FunctionDecl* 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); void DeduceLiveness(); void DeduceMaybeUsed(); @@ -250,6 +257,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_; @@ -258,6 +266,7 @@ class Semantics final tr::vector maybe_used_; SemaContext* sc_ = nullptr; bool pending_heap_allocation_ = false; + ir::NodeListBuilder* ir_ = nullptr; }; class AutoEnterScope final 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; } };