diff --git a/src/compiler/ast.c b/src/compiler/ast.c index 87d1b2d5c..1defb78c3 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -213,6 +213,101 @@ Decl *decl_new_generated_var(const char *name, Type *type, VarDeclKind kind, Sou return decl; } +bool expr_is_pure(Expr *expr) +{ + if (!expr) return true; + switch (expr->expr_kind) + { + case EXPR_BUILTIN: + return false; + case EXPR_CONST: + case EXPR_CONST_IDENTIFIER: + case EXPR_IDENTIFIER: + case EXPR_NOP: + return true; + case EXPR_BITASSIGN: + return false; + case EXPR_BINARY: + if (expr->binary_expr.operator >= BINARYOP_ASSIGN) return false; + return expr_is_pure(expr->binary_expr.right) && expr_is_pure(expr->binary_expr.left); + case EXPR_UNARY: + switch (expr->unary_expr.operator) + { + case UNARYOP_INC: + case UNARYOP_DEC: + case UNARYOP_TADDR: + return false; + default: + return expr_is_pure(expr->unary_expr.expr); + } + break; + case EXPR_BITACCESS: + case EXPR_ACCESS: + return expr_is_pure(expr->access_expr.parent); + case EXPR_POISONED: + case EXPR_CT_IDENT: + case EXPR_TYPEID: + case EXPR_CT_CALL: + UNREACHABLE + case EXPR_MACRO_BODY_EXPANSION: + case EXPR_CALL: + case EXPR_CATCH_UNWRAP: + case EXPR_COMPOUND_LITERAL: + case EXPR_COND: + case EXPR_DESIGNATOR: + case EXPR_DECL: + case EXPR_OR_ERROR: + case EXPR_EXPR_BLOCK: + case EXPR_FAILABLE: + case EXPR_RETHROW: + case EXPR_HASH_IDENT: + case EXPR_MACRO_BLOCK: + case EXPR_MACRO_EXPANSION: + case EXPR_FLATPATH: + case EXPR_INITIALIZER_LIST: + case EXPR_DESIGNATED_INITIALIZER_LIST: + case EXPR_PLACEHOLDER: + case EXPR_POST_UNARY: + case EXPR_SCOPED_EXPR: + case EXPR_SLICE_ASSIGN: + case EXPR_TRY_UNWRAP: + case EXPR_TRY_UNWRAP_CHAIN: + case EXPR_UNDEF: + case EXPR_TYPEINFO: + case EXPR_FORCE_UNWRAP: + return false; + case EXPR_CAST: + return expr_is_pure(expr->cast_expr.expr); + case EXPR_EXPRESSION_LIST: + { + VECEACH(expr->expression_list, i) + { + if (!expr_is_pure(expr->expression_list[i])) return false; + } + return true; + } + break; + case EXPR_LEN: + return expr_is_pure(expr->len_expr.inner); + case EXPR_SLICE: + return expr_is_pure(expr->slice_expr.expr) + && expr_is_pure(expr->slice_expr.start) + && expr_is_pure(expr->slice_expr.end); + case EXPR_SUBSCRIPT: + return expr_is_pure(expr->subscript_expr.expr) + && expr_is_pure(expr->subscript_expr.index); + case EXPR_TERNARY: + return expr_is_pure(expr->ternary_expr.cond) + && expr_is_pure(expr->ternary_expr.else_expr) + && expr_is_pure(expr->ternary_expr.then_expr); + case EXPR_TRY: + case EXPR_GROUP: + case EXPR_CATCH: + return expr_is_pure(expr->inner_expr); + } + UNREACHABLE +} + bool expr_is_simple(Expr *expr) { RETRY: diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index edb498238..99207e9db 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1178,6 +1178,11 @@ typedef struct Expr *body; } AstAsmStmt; +typedef struct +{ + Expr *scoped; + Ast *stmt; +} AstScopingStmt; typedef struct { Expr *message; @@ -1244,6 +1249,7 @@ typedef struct Ast_ Ast *ct_else_stmt; // 8 AstCtForStmt ct_for_stmt; // 64 AstScopedStmt scoped_stmt; // 16 + AstScopingStmt scoping_stmt; AstAssertStmt ct_assert_stmt; AstAssertStmt assert_stmt; Ast **directives; @@ -1820,6 +1826,7 @@ void diag_verror_range(SourceLocation *location, const char *message, va_list ar #define EXPR_NEW_TOKEN(kind_, tok_) expr_new(kind_, source_span_from_token_id((tok_).id)) Expr *expr_new(ExprKind kind, SourceSpan start); bool expr_is_simple(Expr *expr); +bool expr_is_pure(Expr *expr); static inline bool expr_ok(Expr *expr) { return expr == NULL || expr->expr_kind != EXPR_POISONED; } static inline bool expr_poison(Expr *expr) { expr->expr_kind = EXPR_POISONED; expr->resolve_status = RESOLVE_DONE; return false; } static inline void expr_replace(Expr *expr, Expr *replacement) diff --git a/src/compiler/copying.c b/src/compiler/copying.c index 9d6ff1fa7..7f7674154 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -216,6 +216,10 @@ Ast *copy_ast(Ast *source) Ast *ast = ast_copy(source); switch (source->ast_kind) { + case AST_SCOPING_STMT: + MACRO_COPY_EXPR(ast->scoping_stmt.scoped); + MACRO_COPY_AST(ast->scoping_stmt.stmt); + return ast; case AST_DOCS: MACRO_COPY_AST_LIST(ast->directives); return ast; diff --git a/src/compiler/enums.h b/src/compiler/enums.h index c7b21ccf3..7967592d9 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -71,6 +71,7 @@ typedef enum AST_IF_STMT, AST_NOP_STMT, AST_RETURN_STMT, + AST_SCOPING_STMT, AST_SWITCH_STMT, AST_NEXT_STMT, AST_UNREACHABLE_STMT, @@ -451,6 +452,7 @@ typedef enum TOKEN_NULL, TOKEN_PRIVATE, TOKEN_RETURN, + TOKEN_SCOPING, TOKEN_STATIC, TOKEN_STRUCT, TOKEN_SWITCH, diff --git a/src/compiler/llvm_codegen_debug_info.c b/src/compiler/llvm_codegen_debug_info.c index ab4a9e99b..23f2f6ef6 100644 --- a/src/compiler/llvm_codegen_debug_info.c +++ b/src/compiler/llvm_codegen_debug_info.c @@ -487,6 +487,11 @@ static LLVMMetadataRef llvm_debug_func_type(GenContext *c, Type *type) static inline LLVMMetadataRef llvm_get_debug_type_internal(GenContext *c, Type *type, LLVMMetadataRef scope) { if (type->backend_debug_type) return type->backend_debug_type; + Type *lowered = type_lowering(type); + if (lowered != type) + { + return type->backend_debug_type = llvm_get_debug_type(c, lowered); + } // Consider special handling of UTF8 arrays. switch (type->type_kind) { diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index eeab68536..fee8a5d75 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -965,100 +965,6 @@ void gencontext_emit_scoped_stmt(GenContext *context, Ast *ast) llvm_emit_defer(context, ast->scoped_stmt.defers.start, ast->scoped_stmt.defers.end); } -static bool expr_is_pure(Expr *expr) -{ - if (!expr) return true; - switch (expr->expr_kind) - { - case EXPR_BUILTIN: - TODO - case EXPR_CONST: - case EXPR_CONST_IDENTIFIER: - case EXPR_IDENTIFIER: - case EXPR_NOP: - return true; - case EXPR_BITASSIGN: - return false; - case EXPR_BINARY: - if (expr->binary_expr.operator >= BINARYOP_ASSIGN) return false; - return expr_is_pure(expr->binary_expr.right) && expr_is_pure(expr->binary_expr.left); - case EXPR_UNARY: - switch (expr->unary_expr.operator) - { - case UNARYOP_INC: - case UNARYOP_DEC: - case UNARYOP_TADDR: - return false; - default: - return expr_is_pure(expr->unary_expr.expr); - } - break; - case EXPR_BITACCESS: - case EXPR_ACCESS: - return expr_is_pure(expr->access_expr.parent); - case EXPR_POISONED: - case EXPR_CT_IDENT: - case EXPR_TYPEID: - case EXPR_CT_CALL: - UNREACHABLE - case EXPR_MACRO_BODY_EXPANSION: - case EXPR_CALL: - case EXPR_CATCH_UNWRAP: - case EXPR_COMPOUND_LITERAL: - case EXPR_COND: - case EXPR_DESIGNATOR: - case EXPR_DECL: - case EXPR_OR_ERROR: - case EXPR_EXPR_BLOCK: - case EXPR_FAILABLE: - case EXPR_RETHROW: - case EXPR_HASH_IDENT: - case EXPR_MACRO_BLOCK: - case EXPR_MACRO_EXPANSION: - case EXPR_FLATPATH: - case EXPR_INITIALIZER_LIST: - case EXPR_DESIGNATED_INITIALIZER_LIST: - case EXPR_PLACEHOLDER: - case EXPR_POST_UNARY: - case EXPR_SCOPED_EXPR: - case EXPR_SLICE_ASSIGN: - case EXPR_TRY_UNWRAP: - case EXPR_TRY_UNWRAP_CHAIN: - case EXPR_UNDEF: - case EXPR_TYPEINFO: - case EXPR_FORCE_UNWRAP: - return false; - case EXPR_CAST: - return expr_is_pure(expr->cast_expr.expr); - case EXPR_EXPRESSION_LIST: - { - VECEACH(expr->expression_list, i) - { - if (!expr_is_pure(expr->expression_list[i])) return false; - } - return true; - } - break; - case EXPR_LEN: - return expr_is_pure(expr->len_expr.inner); - case EXPR_SLICE: - return expr_is_pure(expr->slice_expr.expr) - && expr_is_pure(expr->slice_expr.start) - && expr_is_pure(expr->slice_expr.end); - case EXPR_SUBSCRIPT: - return expr_is_pure(expr->subscript_expr.expr) - && expr_is_pure(expr->subscript_expr.index); - case EXPR_TERNARY: - return expr_is_pure(expr->ternary_expr.cond) - && expr_is_pure(expr->ternary_expr.else_expr) - && expr_is_pure(expr->ternary_expr.then_expr); - case EXPR_TRY: - case EXPR_GROUP: - case EXPR_CATCH: - return expr_is_pure(expr->inner_expr); - } - UNREACHABLE -} static inline void llvm_emit_assume(GenContext *c, Expr *expr) { @@ -1381,6 +1287,7 @@ void llvm_emit_stmt(GenContext *c, Ast *ast) case AST_POISONED: case AST_VAR_STMT: case AST_IF_CATCH_SWITCH_STMT: + case AST_SCOPING_STMT: UNREACHABLE case AST_SCOPED_STMT: gencontext_emit_scoped_stmt(c, ast); diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index 1f8392315..ff9da35cc 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -8,6 +8,8 @@ Ast *parse_unreachable_stmt(Context *context); +Ast *parse_scoping_stmt(Context *context); + #pragma mark --- Internal functions @@ -798,6 +800,8 @@ Ast *parse_stmt(Context *context) case TOKEN_IDENT: case TOKEN_CONST_IDENT: return parse_decl_or_expr_stmt(context); + case TOKEN_SCOPING: + return parse_scoping_stmt(context); case TOKEN_VAR: return parse_var_stmt(context); case TOKEN_TLOCAL: // Global means declaration! @@ -991,6 +995,17 @@ Ast *parse_stmt(Context *context) UNREACHABLE } +Ast *parse_scoping_stmt(Context *context) +{ + Ast *ast = AST_NEW_TOKEN(AST_SCOPING_STMT, context->tok); + advance_and_verify(context, TOKEN_SCOPING); + CONSUME_OR(TOKEN_LPAREN, poisoned_ast); + ASSIGN_EXPR_ELSE(ast->scoping_stmt.scoped, parse_expression_list(context), poisoned_ast); + CONSUME_OR(TOKEN_RPAREN, poisoned_ast); + ASSIGN_AST_ELSE(ast->scoping_stmt.stmt, parse_compound_stmt(context), poisoned_ast); + return ast; +} + Ast *parse_unreachable_stmt(Context *context) { Ast *ast = AST_NEW_TOKEN(AST_UNREACHABLE_STMT, context->tok); diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index dc8a0f150..dea2aaa7b 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -3800,8 +3800,6 @@ static inline bool sema_expr_analyse_expr_list(Context *context, Expr *expr) { bool success = true; ByteSize last = vec_size(expr->expression_list) - 1; - bool constant = true; - bool pure = true; VECEACH(expr->expression_list, i) { Expr *checked_expr = expr->expression_list[i]; diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index b78f94edf..d99ac8e31 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -6,7 +6,7 @@ #pragma mark --- Helper functions - +static bool sema_analyse_compound_stmt(Context *context, Ast *statement); static void sema_unwrappable_from_catch_in_else(Context *c, Expr *cond) { @@ -1984,6 +1984,51 @@ bool sema_analyse_ct_assert_stmt(Context *context, Ast *statement) return true; } +static inline bool sema_analyse_scoping_stmt(Context *context, Ast *statement) +{ + Expr **exprs = statement->scoping_stmt.scoped->expression_list; + unsigned scoped_count = vec_size(exprs); + Ast **stmts = 0; + for (unsigned i = 0; i < scoped_count; i++) + { + Expr *expr = exprs[i]; + if (!sema_analyse_expr_lvalue(context, expr)) return false; + if (!expr_is_ltype(expr)) + { + SEMA_ERROR(expr, "Expected an assignable value."); + return false; + } + if (!expr_is_pure(expr)) + { + SEMA_ERROR(expr, "A value with side effects (e.g. function calls) is not allowed here."); + return false; + } + Decl *new_decl = decl_new_generated_var(".scope", expr->type, VARDECL_LOCAL, expr->span); + new_decl->var.init_expr = expr; + Ast *declare = new_ast(AST_DECLARE_STMT, expr->span); + declare->declare_stmt = new_decl; + vec_add(stmts, declare); + Ast *defer_restore = new_ast(AST_DEFER_STMT, expr->span); + + Expr *restore_expr = expr_new(EXPR_BINARY, expr->span); + Expr *rhs = expr_new(EXPR_IDENTIFIER, expr->span); + rhs->resolve_status = RESOLVE_DONE; + rhs->identifier_expr.decl = new_decl; + rhs->type = expr->type; + + restore_expr->binary_expr = (ExprBinary) { .left = MACRO_COPY_EXPR(expr), .right = rhs, .operator = BINARYOP_ASSIGN }; + Ast *restore_stmt = new_ast(AST_EXPR_STMT, expr->span); + restore_stmt->expr_stmt = restore_expr; + + defer_restore->defer_stmt.body = restore_stmt; + vec_add(stmts, defer_restore); + } + vec_add(stmts, statement->scoping_stmt.stmt); + statement->ast_kind = AST_COMPOUND_STMT; + statement->compound_stmt = (AstCompoundStmt) { .stmts = stmts }; + return sema_analyse_compound_stmt(context, statement); +} + bool sema_analyse_assert_stmt(Context *context, Ast *statement) { Expr *expr = statement->assert_stmt.expr; @@ -2059,6 +2104,8 @@ static inline bool sema_analyse_statement_inner(Context *context, Ast *statement case AST_DOC_DIRECTIVE: case AST_IF_CATCH_SWITCH_STMT: UNREACHABLE + case AST_SCOPING_STMT: + return sema_analyse_scoping_stmt(context, statement); case AST_ASM_STMT: return sema_analyse_asm_stmt(context, statement); case AST_ASSERT_STMT: diff --git a/src/compiler/tokens.c b/src/compiler/tokens.c index c81cf357d..f2e0a946b 100644 --- a/src/compiler/tokens.c +++ b/src/compiler/tokens.c @@ -252,6 +252,8 @@ const char *token_type_to_string(TokenType type) return "private"; case TOKEN_RETURN: return "return"; + case TOKEN_SCOPING: + return "scoping"; case TOKEN_STATIC: return "static"; case TOKEN_STRUCT: diff --git a/src/version.h b/src/version.h index 3f814420d..15a1c727d 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "PRE.2" \ No newline at end of file +#define COMPILER_VERSION "PRE.3" \ No newline at end of file diff --git a/test/src/tester.py b/test/src/tester.py index 159dc81fa..30de44857 100644 --- a/test/src/tester.py +++ b/test/src/tester.py @@ -52,6 +52,7 @@ class Issues: self.error_message = "unknown" self.skip = False self.cur = 0 + self.debuginfo = False self.arch = None self.current_file = None self.files = [] @@ -103,9 +104,12 @@ class Issues: def compile(self, args): os.chdir(TEST_DIR) target = "" + debug = "" if (self.arch): target = " --target " + self.arch - code = subprocess.run(self.conf.compiler + target + ' -O0 ' + args, universal_newlines=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if (self.debuginfo): + debug = "-g " + code = subprocess.run(self.conf.compiler + target + ' -O0 ' + debug + args, universal_newlines=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) os.chdir(self.conf.cwd) if code.returncode != 0 and code.returncode != 1: self.set_failed() @@ -134,6 +138,9 @@ class Issues: def parse_header_directive(self, line): line = line[4:].strip() + if (line.startswith("debuginfo:")): + self.debuginfo = line[10:].strip() == "yes" + return if (line.startswith("target:")): self.arch = line[7:].strip() return diff --git a/test/test_suite/debug_symbols/constants.c3t b/test/test_suite/debug_symbols/constants.c3t new file mode 100644 index 000000000..1195ee63b --- /dev/null +++ b/test/test_suite/debug_symbols/constants.c3t @@ -0,0 +1,28 @@ +// #target: x64-darwin +// #debuginfo: yes +private const char AA = 1; +const char BB = 200 ; +private const uint CC = ~(uint)(0); +private const FOO = ~(uint)(0); + +/* #expect: constants.ll + +@constants.AA = protected constant i8 1, align 1 +@constants.BB = constant i8 -56, align 1 +@constants.CC = protected constant i32 -1, align 4 +@constants.FOO = protected constant i32 -1, align 4 +!llvm.dbg.cu = !{!0} +!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "c3c", isOptimized: false, runtimeVersion: 1, emissionKind: FullDebug, enums: !2, globals: !3, splitDebugInlining: false) +!1 = !DIFile(filename: "constants.c3", +!2 = !{} +!3 = !{!4, !7, !9, !12} +!4 = !DIGlobalVariableExpression(var: !5, expr: !DIExpression()) +!5 = distinct !DIGlobalVariable(name: "AA", linkageName: "constants.AA", scope: !1, file: !1, line: 1, type: !6, isLocal: false, isDefinition: true, align: 1) +!6 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_unsigned_char) +!7 = !DIGlobalVariableExpression(var: !8, expr: !DIExpression()) +!8 = distinct !DIGlobalVariable(name: "BB", linkageName: "constants.BB", scope: !1, file: !1, line: 2, type: !6, isLocal: false, isDefinition: true, align: 1) +!9 = !DIGlobalVariableExpression(var: !10, expr: !DIExpression()) +!10 = distinct !DIGlobalVariable(name: "CC", linkageName: "constants.CC", scope: !1, file: !1, line: 3, type: !11, isLocal: false, isDefinition: true, align: 4) +!11 = !DIBasicType(name: "uint", size: 32, encoding: DW_ATE_unsigned) +!12 = !DIGlobalVariableExpression(var: !13, expr: !DIExpression()) +!13 = distinct !DIGlobalVariable(name: "FOO", linkageName: "constants.FOO", scope: !1, file: !1, line: 4, type: !11, isLocal: false, isDefinition: true, align: 4) diff --git a/test/test_suite/scoping/general_scoping.c3t b/test/test_suite/scoping/general_scoping.c3t new file mode 100644 index 000000000..a56d736fc --- /dev/null +++ b/test/test_suite/scoping/general_scoping.c3t @@ -0,0 +1,79 @@ +module test; +extern fn void printf(char*, ...); + +struct Foo +{ + struct goo + { + int* z; + } +} + +Foo bob; +fn void main() +{ + int y = 7; + bob.goo.z = &y; + int x = 1; + scoping (x, *bob.goo.z) + { + x = 3; + *bob.goo.z = 12; + printf("%d %d\n", x, *bob.goo.z); + } + printf("%d %d\n", x, *bob.goo.z); +} + +/* #expect: test.ll + +%Foo = type { %goo } +%goo = type { i32* } + +@goo = linkonce_odr constant i8 1 +@Foo = linkonce_odr constant i8 1 +@test.bob = global %Foo zeroinitializer, align 8 +@.str = private constant [7 x i8] c"%d %d\0A\00", align 1 +@.str.1 = private constant [7 x i8] c"%d %d\0A\00", align 1 + +; Function Attrs: nounwind +declare void @printf(i8*, ...) #0 + +; Function Attrs: nounwind +define void @main() #0 { +entry: + %y = alloca i32, align 4 + %x = alloca i32, align 4 + %.scope = alloca i32, align 4 + %.scope1 = alloca i32, align 4 + store i32 7, i32* %y, align 4 + store i32* %y, i32** getelementptr inbounds (%Foo, %Foo* @test.bob, i32 0, i32 0, i32 0), align 8 + store i32 1, i32* %x, align 4 + %0 = load i32, i32* %x, align 4 + store i32 %0, i32* %.scope, align 4 + %1 = load i32*, i32** getelementptr inbounds (%Foo, %Foo* @test.bob, i32 0, i32 0, i32 0), align 8 + %2 = load i32, i32* %1, align 8 + store i32 %2, i32* %.scope1, align 4 + store i32 3, i32* %x, align 4 + %3 = load i32*, i32** getelementptr inbounds (%Foo, %Foo* @test.bob, i32 0, i32 0, i32 0), align 8 + store i32 12, i32* %3, align 8 + %4 = load i32, i32* %x, align 4 + %5 = load i32*, i32** getelementptr inbounds (%Foo, %Foo* @test.bob, i32 0, i32 0, i32 0), align 8 + %6 = load i32, i32* %5, align 8 + call void (i8*, ...) @printf(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i32 0, i32 0), i32 %4, i32 %6) + %7 = load i32*, i32** getelementptr inbounds (%Foo, %Foo* @test.bob, i32 0, i32 0, i32 0), align 8 + %8 = load i32, i32* %.scope1, align 4 + store i32 %8, i32* %7, align 8 + br label %exit + +exit: ; preds = %entry + %9 = load i32, i32* %.scope, align 4 + store i32 %9, i32* %x, align 4 + br label %exit2 + +exit2: ; preds = %exit + %10 = load i32, i32* %x, align 4 + %11 = load i32*, i32** getelementptr inbounds (%Foo, %Foo* @test.bob, i32 0, i32 0, i32 0), align 8 + %12 = load i32, i32* %11, align 8 + call void (i8*, ...) @printf(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.1, i32 0, i32 0), i32 %10, i32 %12) + ret void +}