diff --git a/releasenotes.md b/releasenotes.md new file mode 100644 index 000000000..6b41c7d28 --- /dev/null +++ b/releasenotes.md @@ -0,0 +1,42 @@ +Release Notes + +0.4.0 Change List + +- Compatibility with LLVM 16. +- Dropped LLVM 12 support. +- Updated memory allocators. Added @scoped and @pool macros. +- Various bug fixes. +- Constant pointers may be compile time evaluated. +- Added many new builtins. +- Emit asm using --emit-asm. +- Added --nostdlib and --nolibc. +- Fixes to adding libraries at link time. +- Various improved error messages. +- Windows debug info fixes. +- Add of `foreach_r`. +- Script downloading the MSVC SDK to cross compile to windows. +- Many standard library additions. +- Extension methods may be added for built-in types. +- Macros may take vector and array arguments generic over length. +- Macro varargs with $vaarg, $vacount etc. +- Many vector builtins added as dot methods. +- in / out / inout doc parameters checked. +- Initial inline asm support for aarch64 and x64. +- Single line short function declaration. +- Added `$checks` builtin. +- Optional single module compilation. +- Static initialization / finalization to have code running at start/end. +- C3 custom printf function in the stdlib. +- `[]=` overload now works correctly. +- More compile time reflection added and general cleanup done. +- usize/isize/iptrdiff/uptrdiff replaced by usz/isz. +- Add `var` to allow type inference on regular variables. +- LLVM codegen optimizations. +- `??` now allows chaining another optional. +- int128 support on all platforms. +- `import` is now allowed anywhere at the top level. +- `project.c3p` renamed `project.json` +- Update to project properties, e.g. "libs" -> "dependencies" etc. +- $$TIME, $$DATE and $$FUNCTION builtin defines added. +- Improvements to untyped lists. +- Various builtins added: $$prefetch, $$reverse, $$shufflevector etc. \ No newline at end of file diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 5ce2b79ff..738c85c37 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -609,7 +609,7 @@ typedef struct SourceSpan span; }; }; - TypeInfo **generic_params; + Expr **generic_params; }; Decl *alias; }; @@ -1451,7 +1451,7 @@ typedef struct Module_ AnalysisStage stage : 6; Ast **files; // Asts - + AstId docs; Decl** method_extensions; HTable symbols; struct CompilationUnit_ **units; diff --git a/src/compiler/copying.c b/src/compiler/copying.c index f5e61e352..71fdcf9a0 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -901,7 +901,7 @@ Decl *copy_decl(CopyStruct *c, Decl *decl) { case DEFINE_TYPE_GENERIC: case DEFINE_IDENT_GENERIC: - MACRO_COPY_TYPE_LIST(decl->define_decl.generic_params); + MACRO_COPY_EXPR_LIST(decl->define_decl.generic_params); break; case DEFINE_IDENT_ALIAS: break; diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index 739302784..f9df35b1d 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -89,6 +89,11 @@ static Expr *parse_precedence(ParseContext *c, Precedence precedence) return parse_precedence_with_left_side(c, left_side, precedence); } +Expr *parse_generic_parameter(ParseContext *c) +{ + return parse_precedence(c, PREC_ADDITIVE); +} + Expr *parse_expr_or_initializer_list(ParseContext *c) { return parse_expr(c); diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index 0cfce026d..bb78dc5ed 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -90,7 +90,7 @@ static inline bool parse_top_level_block(ParseContext *c, Decl ***decls, TokenTy CONSUME_OR_RET(TOKEN_COLON, false); while (!tok_is(c, end1) && !tok_is(c, end2) && !tok_is(c, end3) && !tok_is(c, TOKEN_EOF)) { - Decl *decl = parse_top_level_statement(c, false); + Decl *decl = parse_top_level_statement(c, NULL); if (!decl) continue; assert(decl); if (decl_ok(decl)) @@ -173,7 +173,7 @@ static inline Decl *parse_ct_case(ParseContext *c) { TokenType type = c->tok; if (type == TOKEN_CT_DEFAULT || type == TOKEN_CT_CASE || type == TOKEN_CT_ENDSWITCH) break; - ASSIGN_DECL_OR_RET(Decl * stmt, parse_top_level_statement(c, false), poisoned_decl); + ASSIGN_DECL_OR_RET(Decl *stmt, parse_top_level_statement(c, NULL), poisoned_decl); vec_add(decl->ct_case_decl.body, stmt); } return decl; @@ -282,12 +282,13 @@ static inline bool parse_optional_module_params(ParseContext *c, const char ***t switch (c->tok) { case TOKEN_TYPE_IDENT: + case TOKEN_CONST_IDENT: break; case TOKEN_COMMA: SEMA_ERROR_HERE("Unexpected ','"); return false; case TOKEN_IDENT: - SEMA_ERROR_HERE("The module parameter must be a type."); + SEMA_ERROR_HERE("The module parameter must be a type or a constant."); return false; case TOKEN_CT_IDENT: case TOKEN_CT_TYPE_IDENT: @@ -309,13 +310,8 @@ static inline bool parse_optional_module_params(ParseContext *c, const char ***t /** * module ::= MODULE module_path ('<' module_params '>')? EOS */ -bool parse_module(ParseContext *c) +bool parse_module(ParseContext *c, AstId docs) { - if (!try_consume(c, TOKEN_MODULE)) - { - return context_set_module_from_filename(c); - } - bool is_private = try_consume(c, TOKEN_PRIVATE); if (tok_is(c, TOKEN_STRING)) @@ -359,11 +355,30 @@ bool parse_module(ParseContext *c) const char **generic_parameters = NULL; if (!parse_optional_module_params(c, &generic_parameters)) { - context_set_module(c, path, NULL, is_private); + if (!context_set_module(c, path, NULL, is_private)) return false; recover_top_level(c); + if (docs) + { + SEMA_ERROR(astptr(docs), "Contracts cannot be use with non-generic modules."); + return false; + } return true; } - context_set_module(c, path, generic_parameters, is_private); + if (!context_set_module(c, path, generic_parameters, is_private)) return false; + if (docs) + { + AstId old_docs = c->unit->module->docs; + if (old_docs) + { + Ast *last = ast_last(astptr(old_docs)); + last->next = docs; + } + else + { + c->unit->module->docs = docs; + } + } + CONSUME_EOS_OR_RET(false); return true; } @@ -1629,24 +1644,24 @@ static bool parse_macro_arguments(ParseContext *c, Visibility visibility, Decl * } /** - * define_parameters ::= type (',' type)* '>' + * define_parameters ::= expr (',' expr)* '>' * * @return NULL if parsing failed, otherwise a list of Type* */ -static inline TypeInfo **parse_generic_parameters(ParseContext *c) +static inline Expr **parse_generic_parameters(ParseContext *c) { - TypeInfo **types = NULL; + Expr **params = NULL; while (!try_consume(c, TOKEN_GREATER)) { - ASSIGN_TYPE_OR_RET(TypeInfo *type_info, parse_type(c), NULL); - vec_add(types, type_info); + ASSIGN_EXPR_OR_RET(Expr *arg, parse_generic_parameter(c), NULL); + vec_add(params, arg); TokenType tok = c->tok; if (tok != TOKEN_RPAREN && tok != TOKEN_GREATER) { TRY_CONSUME_OR_RET(TOKEN_COMMA, "Expected ',' after argument.", NULL); } } - return types; + return params; } /** @@ -1693,7 +1708,7 @@ static inline Decl *parse_define_type(ParseContext *c, Visibility visibility) // 3. Do we have '<' if so it's a parameterized type e.g. foo::bar::Type. if (try_consume(c, TOKEN_LESS)) { - TypeInfo **params = parse_generic_parameters(c); + Expr **params = parse_generic_parameters(c); if (!params) return poisoned_decl; Decl *decl = decl_new(DECL_DEFINE, alias_name, name_loc, visibility); decl->define_decl.define_kind = DEFINE_TYPE_GENERIC; @@ -1796,7 +1811,7 @@ static inline Decl *parse_define_ident(ParseContext *c, Visibility visibility) if (try_consume(c, TOKEN_LESS)) { decl->define_decl.define_kind = DEFINE_IDENT_GENERIC; - TypeInfo **params = parse_generic_parameters(c); + Expr **params = parse_generic_parameters(c); if (!params) return poisoned_decl; decl->define_decl.generic_params = params; } @@ -2519,7 +2534,7 @@ static bool parse_docs(ParseContext *c, AstId *docs_ref) * @param visibility * @return Decl* or a poison value if parsing failed */ -Decl *parse_top_level_statement(ParseContext *c, bool allow_import) +Decl *parse_top_level_statement(ParseContext *c, ParseContext **c_ref) { AstId docs = 0; if (!parse_docs(c, &docs)) return poisoned_decl; @@ -2539,8 +2554,35 @@ Decl *parse_top_level_statement(ParseContext *c, bool allow_import) Decl *decl; TokenType tok = c->tok; + if (tok != TOKEN_MODULE && !c->unit->module) + { + if (!context_set_module_from_filename(c)) return poisoned_decl; + // Pass the docs to the next thing. + } + switch (tok) { + case TOKEN_MODULE: + if (visibility != VISIBLE_PUBLIC) + { + SEMA_ERROR_HERE("Did not expect visibility before 'module'."); + return poisoned_decl; + } + if (!c_ref) + { + SEMA_ERROR_HERE("'module' cannot appear inside of conditional compilation."); + return poisoned_decl; + } + advance(c); + if (c->unit->module) + { + ParseContext *new_context = CALLOCS(ParseContext); + *new_context = *c; + new_context->unit = unit_create(c->unit->file); + *c_ref = c = new_context; + } + if (!parse_module(c, docs)) return poisoned_decl; + return NULL; case TOKEN_DOCS_START: if (visibility != VISIBLE_PUBLIC) { @@ -2596,7 +2638,7 @@ Decl *parse_top_level_statement(ParseContext *c, bool allow_import) } case TOKEN_IMPORT: if (!check_no_visibility_before(c, visibility)) return poisoned_decl; - if (!allow_import) + if (!c_ref) { SEMA_ERROR_HERE("'import' may not appear inside a compile time statement."); return poisoned_decl; diff --git a/src/compiler/parser.c b/src/compiler/parser.c index 1b20cfd55..681d70c3e 100644 --- a/src/compiler/parser.c +++ b/src/compiler/parser.c @@ -64,19 +64,9 @@ static inline void parse_translation_unit(ParseContext *c) // Prime everything advance(c); advance(c); - NEXT_CONTEXT: - if (!parse_module(c)) return; while (!tok_is(c, TOKEN_EOF)) { - if (tok_is(c, TOKEN_MODULE)) - { - ParseContext *new_context = CALLOCS(ParseContext); - *new_context = *c; - new_context->unit = unit_create(c->unit->file); - c = new_context; - goto NEXT_CONTEXT; - } - Decl *decl = parse_top_level_statement(c, true); + Decl *decl = parse_top_level_statement(c, &c); if (!decl) continue; if (decl_ok(decl)) { diff --git a/src/compiler/parser_internal.h b/src/compiler/parser_internal.h index 2b6a76696..1d4a41dff 100644 --- a/src/compiler/parser_internal.h +++ b/src/compiler/parser_internal.h @@ -16,7 +16,7 @@ #define TRY_CONSUME_AFTER(_tok, _message, _type) do { if (!try_consume(c, _tok)) { sema_error_at_after(c->prev_span, _message); return _type; } } while(0) #define CHECK_EXPR_OR_RET(_expr) do { if (!expr_ok(_expr)) return _expr; } while(0) -Decl *parse_top_level_statement(ParseContext *c, bool allow_import); +Decl *parse_top_level_statement(ParseContext *c, ParseContext **new_context); Ast *parse_ct_assert_stmt(ParseContext *c); Ast *parse_stmt(ParseContext *c); Path *parse_path_prefix(ParseContext *c, bool *had_error); @@ -52,8 +52,8 @@ parse_parameters(ParseContext *c, Visibility visibility, Decl ***params_ref, Dec bool parse_arg_list(ParseContext *c, Expr ***result, TokenType param_end, bool *splat, bool vasplat); Expr *parse_type_compound_literal_expr_after_type(ParseContext *c, TypeInfo *type_info); -bool parse_module(ParseContext *c); - +bool parse_module(ParseContext *c, AstId docs); +Expr *parse_generic_parameter(ParseContext *c); bool try_consume(ParseContext *c, TokenType type); bool consume(ParseContext *c, TokenType type, const char *message, ...); bool consume_const_name(ParseContext *c, const char* type); diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index f9fc1255b..f09a02053 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -51,7 +51,7 @@ static inline bool sema_analyse_distinct(SemaContext *context, Decl *decl); static CompilationUnit *unit_copy(Module *module, CompilationUnit *unit); static bool sema_analyse_parameterized_define(SemaContext *c, Decl *decl); -static Module *module_instantiate_generic(Module *module, Path *path, TypeInfo **parms); +static Module *module_instantiate_generic(Module *module, Path *path, Expr **params); static inline bool sema_analyse_enum_param(SemaContext *context, Decl *param, bool *has_default); static inline bool sema_analyse_enum(SemaContext *context, Decl *decl); @@ -2500,7 +2500,6 @@ bool sema_analyse_var_decl(SemaContext *context, Decl *decl, bool local) } - static CompilationUnit *unit_copy(Module *module, CompilationUnit *unit) { CompilationUnit *copy = unit_create(unit->file); @@ -2511,7 +2510,7 @@ static CompilationUnit *unit_copy(Module *module, CompilationUnit *unit) return copy; } -static Module *module_instantiate_generic(Module *module, Path *path, TypeInfo **parms) +static Module *module_instantiate_generic(Module *module, Path *path, Expr **params) { Module *new_module = compiler_find_or_create_module(path, NULL, module->is_private); new_module->is_generic = true; @@ -2523,10 +2522,20 @@ static Module *module_instantiate_generic(Module *module, Path *path, TypeInfo * CompilationUnit *first_context = new_module->units[0]; VECEACH(module->parameters, i) { - const char *param = module->parameters[i]; - Decl *decl = decl_new_with_type(param, parms[i]->span, DECL_TYPEDEF, VISIBLE_PUBLIC); + const char *param_name = module->parameters[i]; + Expr *param = params[i]; + if (param->expr_kind != EXPR_TYPEINFO) + { + Decl *decl = decl_new_var(param_name, param->span, NULL, VARDECL_CONST, VISIBLE_PUBLIC); + decl->var.init_expr = param; + decl->type = param->type; + decl->resolve_status = RESOLVE_NOT_DONE; + vec_add(first_context->global_decls, decl); + continue; + } + Decl *decl = decl_new_with_type(param_name, params[i]->span, DECL_TYPEDEF, VISIBLE_PUBLIC); decl->resolve_status = RESOLVE_DONE; - TypeInfo *type_info = parms[i]; + TypeInfo *type_info = param->type_expr; assert(type_info->resolve_status == RESOLVE_DONE); decl->typedef_decl.type_info = type_info; decl->type->name = decl->name; @@ -2576,7 +2585,7 @@ static bool sema_analyse_parameterized_define(SemaContext *c, Decl *decl) } Module *module = alias->unit->module; - TypeInfo **params = decl->define_decl.generic_params; + Expr **params = decl->define_decl.generic_params; unsigned parameter_count = vec_size(module->parameters); assert(parameter_count > 0); if (parameter_count != vec_size(params)) @@ -2591,10 +2600,62 @@ static bool sema_analyse_parameterized_define(SemaContext *c, Decl *decl) scratch_buffer_clear(); scratch_buffer_append_len(module->name->module, module->name->len); scratch_buffer_append("$$"); - FOREACH_BEGIN_IDX(i, TypeInfo *type_info, decl->define_decl.generic_params) - if (!sema_resolve_type_info(c, type_info)) return decl_poison(decl); + FOREACH_BEGIN_IDX(i, Expr *param, decl->define_decl.generic_params) if (i != 0) scratch_buffer_append_char('.'); - type_mangle_introspect_name_to_buffer(type_info->type->canonical); + if (param->expr_kind == EXPR_TYPEINFO) + { + TypeInfo *type = param->type_expr; + if (!sema_resolve_type_info(c, type)) return decl_poison(decl); + if (type->kind == TYPE_OPTIONAL) + { + SEMA_ERROR(type, "Expected a non-optional type."); + return poisoned_decl; + } + if (type_is_invalid_storage_type(type->type)) + { + SEMA_ERROR(type, "Expected a runtime type."); + return poisoned_decl; + } + type_mangle_introspect_name_to_buffer(type->type->canonical); + } + else + { + if (!sema_analyse_ct_expr(c, param)) return decl_poison(decl); + Type *type = param->type->canonical; + if (!type_is_integer_or_bool_kind(type)) + { + SEMA_ERROR(param, "Only integer and boolean types may be generic arguments."); + return poisoned_decl; + } + assert(expr_is_const(param)); + if (type == type_bool) + { + scratch_buffer_append_char(param->const_expr.b ? 't' : 'f'); + } + else + { + char *maybe_neg = &scratch_buffer.str[scratch_buffer.len]; + if (type->type_kind == TYPE_I128 || type->type_kind == TYPE_U128) + { + char *str = int_to_str(param->const_expr.ixx, 10); + + scratch_buffer_append(str); + } + else + { + if (type_is_signed(type)) + { + scratch_buffer_append_signed_int(param->const_expr.ixx.i.low); + } + else + { + scratch_buffer_append_unsigned_int(param->const_expr.ixx.i.high); + } + } + // Replace - with _ + if (maybe_neg[0] == '-') maybe_neg[0] = '_'; + } + } FOREACH_END(); TokenType ident_type = TOKEN_IDENT; const char *path_string = scratch_buffer_interned(); diff --git a/src/version.h b/src/version.h index 24334587e..3e2ef0251 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.3.95" \ No newline at end of file +#define COMPILER_VERSION "0.3.96" \ No newline at end of file diff --git a/test/test_suite/generic/generic_num.c3t b/test/test_suite/generic/generic_num.c3t new file mode 100644 index 000000000..5238618a3 --- /dev/null +++ b/test/test_suite/generic/generic_num.c3t @@ -0,0 +1,61 @@ +// #target: macos-x64 +module hello; + +fn Type x(Type t) +{ + return t * t + FOO; +} + +module test; +import hello; +define xint = hello::x; + +import std::io; + +fn void main() +{ + io::printfln("%d", xint(4)); +} + +/* #expect: test.ll + +define void @test_main() #0 { +entry: + %retparam = alloca i64, align 8 + %taddr = alloca %"char[]", align 8 + %vararg = alloca %"variant[]", align 8 + %varargslots = alloca [1 x %variant], align 16 + %taddr1 = alloca i32, align 4 + store %"char[]" { i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i32 0, i32 0), i64 2 }, %"char[]"* %taddr, align 8 + %0 = bitcast %"char[]"* %taddr to { i8*, i64 }* + %1 = getelementptr inbounds { i8*, i64 }, { i8*, i64 }* %0, i32 0, i32 0 + %lo = load i8*, i8** %1, align 8 + %2 = getelementptr inbounds { i8*, i64 }, { i8*, i64 }* %0, i32 0, i32 1 + %hi = load i64, i64* %2, align 8 + %3 = call i32 @"hello$$int._123_x"(i32 4) + store i32 %3, i32* %taddr1, align 4 + %4 = bitcast i32* %taddr1 to i8* + %5 = insertvalue %variant undef, i8* %4, 0 + %6 = insertvalue %variant %5, i64 ptrtoint (%.introspect* @"ct$int" to i64), 1 + %7 = getelementptr inbounds [1 x %variant], [1 x %variant]* %varargslots, i64 0, i64 0 + store %variant %6, %variant* %7, align 16 + %8 = getelementptr inbounds %"variant[]", %"variant[]"* %vararg, i32 0, i32 1 + store i64 1, i64* %8, align 8 + %9 = getelementptr inbounds %"variant[]", %"variant[]"* %vararg, i32 0, i32 0 + %10 = bitcast [1 x %variant]* %varargslots to %variant* + store %variant* %10, %variant** %9, align 8 + %11 = bitcast %"variant[]"* %vararg to { i8*, i64 }* + %12 = getelementptr inbounds { i8*, i64 }, { i8*, i64 }* %11, i32 0, i32 0 + %lo2 = load i8*, i8** %12, align 8 + %13 = getelementptr inbounds { i8*, i64 }, { i8*, i64 }* %11, i32 0, i32 1 + %hi3 = load i64, i64* %13, align 8 + %14 = call i64 @std_io_printfln(i64* %retparam, i8* %lo, i64 %hi, i8* %lo2, i64 %hi3) + %not_err = icmp eq i64 %14, 0 + br i1 %not_err, label %after_check, label %voiderr + +after_check: ; preds = %entry + br label %voiderr + +voiderr: ; preds = %after_check, %entry + ret void +} \ No newline at end of file diff --git a/test/test_suite2/generic/generic_num.c3t b/test/test_suite2/generic/generic_num.c3t new file mode 100644 index 000000000..557dfafd4 --- /dev/null +++ b/test/test_suite2/generic/generic_num.c3t @@ -0,0 +1,58 @@ +// #target: macos-x64 +module hello; + +fn Type x(Type t) +{ + return t * t + FOO; +} + +module test; +import hello; +define xint = hello::x; + +import std::io; + +fn void main() +{ + io::printfln("%d", xint(4)); +} + +/* #expect: test.ll + +define void @test_main() #0 { +entry: + %retparam = alloca i64, align 8 + %taddr = alloca %"char[]", align 8 + %vararg = alloca %"variant[]", align 8 + %varargslots = alloca [1 x %variant], align 16 + %taddr1 = alloca i32, align 4 + store %"char[]" { ptr @.str, i64 2 }, ptr %taddr, align 8 + %0 = getelementptr inbounds { ptr, i64 }, ptr %taddr, i32 0, i32 0 + %lo = load ptr, ptr %0, align 8 + %1 = getelementptr inbounds { ptr, i64 }, ptr %taddr, i32 0, i32 1 + %hi = load i64, ptr %1, align 8 + %2 = call i32 @"hello$$int._123_x"(i32 4) + store i32 %2, ptr %taddr1, align 4 + %3 = insertvalue %variant undef, ptr %taddr1, 0 + %4 = insertvalue %variant %3, i64 ptrtoint (ptr @"ct$int" to i64), 1 + %5 = getelementptr inbounds [1 x %variant], ptr %varargslots, i64 0, i64 0 + store %variant %4, ptr %5, align 16 + %6 = getelementptr inbounds %"variant[]", ptr %vararg, i32 0, i32 1 + store i64 1, ptr %6, align 8 + %7 = getelementptr inbounds %"variant[]", ptr %vararg, i32 0, i32 0 + store ptr %varargslots, ptr %7, align 8 + %8 = getelementptr inbounds { ptr, i64 }, ptr %vararg, i32 0, i32 0 + %lo2 = load ptr, ptr %8, align 8 + %9 = getelementptr inbounds { ptr, i64 }, ptr %vararg, i32 0, i32 1 + %hi3 = load i64, ptr %9, align 8 + %10 = call i64 @std_io_printfln(ptr %retparam, ptr %lo, i64 %hi, ptr %lo2, i64 %hi3) + %not_err = icmp eq i64 %10, 0 + br i1 %not_err, label %after_check, label %voiderr + +after_check: ; preds = %entry + br label %voiderr + +voiderr: ; preds = %after_check, %entry + ret void +} +