diff --git a/daslib/aot_cpp.das b/daslib/aot_cpp.das index b13d93ecdc..8e3e21fbd3 100644 --- a/daslib/aot_cpp.das +++ b/daslib/aot_cpp.das @@ -1304,6 +1304,60 @@ def public describeCppFunc(fn : FunctionPtr; var collector : BlockVariableCollec } } +enum private ExitKind { None, Break, Continue, Return } + +struct private TryExits { + hasBreak : bool + hasContinue : bool + hasReturn : bool +} + +struct private TryFlowLevel { + isLoop : bool + exits : TryExits + flag : string + slot : string +} + +struct private TryFlowState { + stack : array + ret_slot : string + ret_ptr : bool +} + +class TryExitScanner : AstVisitor { + found : TryExits; + loopDepth : int = 0; + + def override preVisitExprForBody(ffor : ExprFor?) { + loopDepth ++; + } + def override visitExprFor(var ffor : ExprFor?) : ExpressionPtr { + loopDepth --; + return ffor; + } + def override preVisitExprWhileBody(wh : ExprWhile?; body : ExpressionPtr) { + loopDepth ++; + } + def override visitExprWhile(var wh : ExprWhile?) : ExpressionPtr { + loopDepth --; + return wh; + } + def override preVisitExprBreak(that : ExprBreak?) { + found.hasBreak ||= loopDepth == 0; + } + def override preVisitExprContinue(that : ExprContinue?) { + found.hasContinue ||= loopDepth == 0; + } + def override preVisitExprReturn(expr : ExprReturn?) { + found.hasReturn ||= expr.returnFunc != null; + } +}; + +def private anyExit(exits : TryExits) { + return exits.hasBreak || exits.hasContinue || exits.hasReturn; +} + class public CppAot : AstVisitor { //! Main AST visitor that generates C++ ahead-of-time compiled code from daslang AST. @@ -1333,6 +1387,7 @@ class public CppAot : AstVisitor { aotPrefix : table; @do_not_delete local_temp_names : table; @do_not_delete scopes : array; + flow : TryFlowState; prologue : bool = false; solidContext : bool = false; cross_platform : bool = false; @@ -1451,7 +1506,12 @@ class public CppAot : AstVisitor { } def write_local_temp_type(tmp : Expression?) : void { write(*ss, tabs()) - describeVarLocalCppType(ss, tmp._type, cross_platform) + if (tmp._type.flags.ref) { + write(*ss, describeCppType(tmp._type, DescribeConfig(skip_ref = true, + skip_const = true, cross_platform = cross_platform))) + } else { + describeVarLocalCppType(ss, tmp._type, cross_platform) + } } def override preVisitGlobalLet(prog : ProgramPtr) { @@ -1500,6 +1560,9 @@ class public CppAot : AstVisitor { return !fun.moreFlags.isTemplate; } def override preVisitFunction(fn : FunctionPtr) { + flow.ret_ptr = fn.result.flags.ref; + flow.ret_slot = fn.result.isVoid ? "" : describeCppType(fn.result, DescribeConfig(skip_const = true, + skip_ref = true, cross_platform = cross_platform)) + (flow.ret_ptr ? " *" : ""); if (!empty(aot_filter_function) && (fn.name == aot_filter_function || aotFuncName(fn) == aot_filter_function)) { write(*ss, "\n/*<>*/"); } @@ -2190,8 +2253,15 @@ class public CppAot : AstVisitor { } return that; } + def returnLevel(expr : ExprReturn?) : int { + return expr.returnFunc == null ? -1 : flowLevel(ExitKind.Return); + } def override preVisitExprReturn(expr : ExprReturn?) { - write(*ss, "return "); + let at = returnLevel(expr); + write(*ss, at < 0 ? "return " : "\{ "); + if (at >= 0 && flow.stack[at].slot != "") { + write(*ss, "{flow.stack[at].slot} = {flow.ret_ptr ? "&(" : ""}"); + } if (expr.returnFlags.moveSemantics) { write(*ss, "/* <- */ "); } @@ -2218,17 +2288,24 @@ class public CppAot : AstVisitor { write(*ss, "/* no function/block for return */ "); return expr; } + let at = returnLevel(expr); assume retT = expr.returnFunc != null ? expr.returnFunc.result : expr._block.returnType; if (!retT.isVoid) { write(*ss, ")"); + if (at >= 0 && flow.ret_ptr) { + write(*ss, ")"); + } + } + if (at >= 0) { + write(*ss, "{retT.isVoid ? "" : "; "}{flow.stack[at].flag} = {int(ExitKind.Return)}; return; \}"); } return expr; } def override preVisitExprBreak(that : ExprBreak?) { - write(*ss, "break"); + writeExit(ExitKind.Break, ""); } def override preVisitExprContinue(that : ExprContinue?) { - write(*ss, "continue"); + writeExit(ExitKind.Continue, ""); } def override preVisitExprVar(variable : ExprVar?) { if (variable._type.flags.aotAlias) { @@ -2761,11 +2838,14 @@ class public CppAot : AstVisitor { } return c; } + def override canVisitWithAliasSubexpression(_expr : ExprAssume?) : bool { + return false; + } def override preVisitExprAssume(expr : ExprAssume?) { - write(*ss, "\n#if 0 // with, note optimizations are off\n"); + write(*ss, "\n#if 0 // assume {expr.alias} = {describe(expr.subexpr) |> replace("\n", " ")}\n"); } def override visitExprAssume(var expr : ExprAssume?) : ExpressionPtr { - write(*ss, "\n#endif\n"); + write(*ss, "\n#endif // assume {expr.alias}\n"); return expr; } def override preVisitExprWith(expr : ExprWith?) { @@ -2779,8 +2859,10 @@ class public CppAot : AstVisitor { } def override preVisitExprWhileBody(wh : ExprWhile?; body : ExpressionPtr) { write(*ss, " )\n{tabs()}"); + flow.stack |> push(TryFlowLevel(isLoop = true)); } def override visitExprWhile(var wh : ExprWhile?) : ExpressionPtr { + flow.stack |> pop(); return wh; } def override preVisitExprIfThenElse(ifte : ExprIfThenElse?) { @@ -2986,7 +3068,49 @@ class public CppAot : AstVisitor { write(*ss, ")"); return expr; } + def flowLevel(code : ExitKind) : int { + for (i in range(length(flow.stack))) { + let at = length(flow.stack) - 1 - i; + assume lvl = flow.stack[at]; + if (!lvl.isLoop) return lvl.flag != "" ? at : -1; + if (code != ExitKind.Return) return -1; + } + return -1; + } + def writeExit(code : ExitKind; slot : string) { + let at = flowLevel(code); + if (at >= 0) { + write(*ss, "\{ {flow.stack[at].flag} = {int(code)}; return; \}"); + } elif (code == ExitKind.Break) { + write(*ss, "break"); + } elif (code == ExitKind.Continue) { + write(*ss, "continue"); + } else { + write(*ss, "return {slot == "" ? "" : (flow.ret_ptr ? "*" : "") + slot}"); + } + } + def scanExits(tc : ExprTryCatch?) : TryExits { + var scanner = new TryExitScanner(); + make_visitor(*scanner) $(adp) { + tc.try_block |> visit(adp); + tc.catch_block |> visit(adp); + } + return scanner.found; + } def override preVisitExprTryCatch(tc : ExprTryCatch?) { + var lvl = TryFlowLevel(exits = scanExits(tc)); + if (anyExit(lvl.exits)) { + lvl.flag = "__try_flow_{int(tc.at.line)}_{int(tc.at.column)}"; + write(*ss, "int32_t {lvl.flag} = {int(ExitKind.None)};\n{tabs()}"); + let at = flowLevel(ExitKind.Return); + if (at >= 0) { + lvl.slot = flow.stack[at].slot; + } elif (lvl.exits.hasReturn && flow.ret_slot != "") { + lvl.slot = "__try_ret_{int(tc.at.line)}_{int(tc.at.column)}"; + write(*ss, "{flow.ret_slot} {lvl.slot};\n{tabs()}"); + } + } + flow.stack |> push(lvl); write(*ss, "das_try_recover(__context__, [&]()\n"); write(*ss, "{tabs()}"); } @@ -2996,6 +3120,18 @@ class public CppAot : AstVisitor { } def override visitExprTryCatch(var tc : ExprTryCatch?) : ExpressionPtr { write(*ss, ")"); + let lvl = flow.stack[length(flow.stack) - 1]; + flow.stack |> pop(); + if (!anyExit(lvl.exits)) return tc; + write(*ss, ";"); + let taken = fixed_array(lvl.exits.hasBreak, lvl.exits.hasContinue, lvl.exits.hasReturn); + let codes = fixed_array(ExitKind.Break, ExitKind.Continue, ExitKind.Return); + for (t, code in taken, codes) { + if (!t) continue; + write(*ss, "\n{tabs()}if ( {lvl.flag} == {int(code)} ) \{ "); + writeExit(code, lvl.slot); + write(*ss, "; \}"); + } return tc; } def isDistinctDeref(ptr2ref : ExprPtr2Ref?) { @@ -3083,7 +3219,7 @@ class public CppAot : AstVisitor { let info : TypeInfo? = (expr.ascendFlags.needTypeInfo ? helper.helper |> make_type_info(null, expr.subexpr._type) : null); - if (expr._type.firstType.baseType == Type.tHandle) { + if (expr._type.baseType == Type.tPointer && expr._type.firstType.baseType == Type.tHandle) { write(*ss, "das_ascend_handle<{expr._type.flags.smartPtr},{describeCppType(expr._type,DescribeConfig(skip_ref=true,skip_const=true,cross_platform=cross_platform))}>::make(__context__,"); } else { let type_str = describeCppType(expr._type.firstType, DescribeConfig(skip_ref = true, skip_const = true, cross_platform = cross_platform)); @@ -3932,6 +4068,7 @@ class public CppAot : AstVisitor { write(*ss, "{tabs()}bool {nl} = true;\n"); } def override preVisitExprForBody(ffor : ExprFor?) { + flow.stack |> push(TryFlowLevel(isLoop = true)); let nl = needLoopName(ffor); write(*ss, "{tabs()}for ( ; {nl} ; {nl} = "); for (variable in ffor.iteratorVariables) { @@ -3997,6 +4134,7 @@ class public CppAot : AstVisitor { return that; } def override visitExprFor(var ffor : ExprFor?) : ExpressionPtr { + flow.stack |> pop(); write(*ss, "\n"); for (x in range(ffor.iteratorVariables |> length)) { assume variable = ffor.iteratorVariables[ffor.iteratorVariables |> length - 1 - x]; diff --git a/include/daScript/ast/ast_generate.h b/include/daScript/ast/ast_generate.h index f5e09beeca..6f959ff9d1 100644 --- a/include/daScript/ast/ast_generate.h +++ b/include/daScript/ast/ast_generate.h @@ -273,7 +273,7 @@ namespace das { give variables in the scope of 'expr' block unique names only for the top-level block */ - void giveBlockVariablesUniqueNames ( ExpressionPtr expr ); + bool giveBlockVariablesUniqueNames ( ExpressionPtr expr ); /* replace break and continue of a particular loop diff --git a/include/daScript/misc/vectypes.h b/include/daScript/misc/vectypes.h index 0bcc0c1b81..6850f7ecac 100644 --- a/include/daScript/misc/vectypes.h +++ b/include/daScript/misc/vectypes.h @@ -201,7 +201,7 @@ namespace das } uint32_t base, sh; if ( em < ((127u - 14u) << 23) ) { // subnormal half - sh = 126u - (em >> 23) + 13u + 1u; + sh = 126u - (em >> 23); base = (em & 0x7fffffu) | 0x800000u; } else { // normal half sh = 13u; diff --git a/include/daScript/simulate/sim_policy.h b/include/daScript/simulate/sim_policy.h index 496e56f611..147f3af7f2 100644 --- a/include/daScript/simulate/sim_policy.h +++ b/include/daScript/simulate/sim_policy.h @@ -142,12 +142,12 @@ namespace das { static __forceinline int32_t Mod ( int32_t a, int32_t b, Context & context, LineInfo * at ) { if ( b==0 ) context.throw_error_at(at, "division by zero in modulo"); double A = a, B = b; - return a - int32_t(A/B)*b; + return int32_t(a - int64_t(A/B)*b); } static __forceinline void SetMod ( int32_t & a, int32_t b, Context & context, LineInfo * at ) { if ( b==0 ) context.throw_error_at(at, "division by zero in modulo"); double A = a, B = b; - a = a - int32_t(A/B)*b; + a = int32_t(a - int64_t(A/B)*b); } }; @@ -179,6 +179,7 @@ namespace das { }; struct SimPolicy_Double : SimPolicy_Type, SimPolicy_MathTT { + static __forceinline double Abs ( double a, Context &, LineInfo * ) { return fabs(a); } static __forceinline double Div ( double a, double b, Context &, LineInfo * ) { return a / b; } static __forceinline void SetDiv ( double & a, double b, Context &, LineInfo * ) { a /= b; } static __forceinline double Mod ( double a, double b, Context &, LineInfo * ) { return fmod(a,b); } @@ -232,7 +233,7 @@ namespace das { struct SimPolicy_MathFloat { static __forceinline float Sign ( float a, Context &, LineInfo * ) { return a == 0.0f ? 0.0f : (a > 0.0f) ? 1.0f : -1.0f; } - static __forceinline float Abs ( float a, Context &, LineInfo * ) { return v_extract_x(v_abs(v_set_x(a))); } + static __forceinline float Abs ( float a, Context &, LineInfo * ) { return fabsf(a); } static __forceinline float Floor ( float a, Context &, LineInfo * ) { return v_extract_x(v_floor(v_set_x(a))); } static __forceinline float Ceil ( float a, Context &, LineInfo * ) { return v_extract_x(v_ceil(v_set_x(a))); } static __forceinline float Round ( float a, Context &, LineInfo * ) { return v_extract_x(v_round(v_set_x(a))); } @@ -284,7 +285,7 @@ namespace das { return v_or(v_and(v_splats(1.0f), v_cmp_gt(a, v_zero())), v_and(v_splats(-1.0f), v_cmp_lt(a, v_zero()))); } - static __forceinline vec4f Abs ( vec4f a, Context &, LineInfo * ) { return v_abs(a); } + static __forceinline vec4f Abs ( vec4f a, Context &, LineInfo * ) { return v_andnot(v_msbit(), a); } static __forceinline vec4f Floor ( vec4f a, Context &, LineInfo * ) { return v_floor(a); } static __forceinline vec4f Ceil ( vec4f a, Context &, LineInfo * ) { return v_ceil(a); } static __forceinline vec4f Fract ( vec4f a, Context &, LineInfo * ) { return v_sub(a, v_floor(a)); } diff --git a/modules/dasLLVM/daslib/llvm_exe.das b/modules/dasLLVM/daslib/llvm_exe.das index cac064ead5..c5f910af45 100644 --- a/modules/dasLLVM/daslib/llvm_exe.das +++ b/modules/dasLLVM/daslib/llvm_exe.das @@ -94,6 +94,10 @@ class public CollectExternVisitor : AstVisitor { // Create the initialize_modules() function, position a builder in it, and emit das_ensure_environment // plus the builtin-module registrations (all native modules if register_all, else $ + strings). Returns the builder. + def override canVisitWithAliasSubexpression(expr : ExprAssume?) : bool { + return false + } + def private setup_init_builder(register_all : bool) : LLVMBuilderRef { let init_fn = LLVMGetNamedFunction(g_mod, "initialize_modules") let init_entry = LLVMAppendBasicBlockInContext(ctx, init_fn, "entry") diff --git a/modules/dasLLVM/daslib/llvm_jit.das b/modules/dasLLVM/daslib/llvm_jit.das index fb3f1c0d2a..63d3e7ba07 100644 --- a/modules/dasLLVM/daslib/llvm_jit.das +++ b/modules/dasLLVM/daslib/llvm_jit.das @@ -2982,6 +2982,32 @@ class public LlvmJitVisitor : AstVisitor { LLVMPositionBuilderAtEnd(g_builder, check_end) } + def division_overflow_cond(lval, right : LLVMOpaqueValue?; opType : TypeDeclPtr) : LLVMOpaqueValue? { + if (opType.baseType != Type.tInt && opType.baseType != Type.tInt64) { + panic("division overflow guard on {describe(opType)}") + } + let ty = type_to_llvm_type(opType) + let intMin = 1ul << uint64(LLVMGetIntTypeWidth(ty) - 1u) + var is_min = LLVMBuildICmp(g_builder, LLVMIntPredicate.LLVMIntEQ, lval, + LLVMConstInt(ty, intMin, 0), "cmp_is_intmin") + var is_m1 = LLVMBuildICmp(g_builder, LLVMIntPredicate.LLVMIntEQ, right, + LLVMConstAllOnes(ty), "cmp_is_minus_one") + return LLVMBuildAnd(g_builder, is_min, is_m1, "cmp_div_ovf") + } + + def check_division_overflow(lval, right : LLVMOpaqueValue?; at : LineInfo; opType : TypeDeclPtr) { + var check_ovf = append_basic_block("check_div_ovf") + var check_true = append_basic_block("check_ovf_true") + var check_end = append_basic_block("check_ovf_end") + LLVMBuildBr(g_builder, check_ovf) + LLVMPositionBuilderAtEnd(g_builder, check_ovf) + LLVMBuildCondBr(g_builder, division_overflow_cond(lval, right, opType), check_true, check_end) + LLVMPositionBuilderAtEnd(g_builder, check_true) + build_exception("division overflow", at) + LLVMBuildBr(g_builder, check_end) + LLVMPositionBuilderAtEnd(g_builder, check_end) + } + def isExprIntNZ(expr : ExpressionPtr) { return (((expr is ExprConstInt) && (expr as ExprConstInt).value != 0) || ((expr is ExprConstUInt) && (expr as ExprConstUInt).value != 0u) @@ -3195,6 +3221,15 @@ class public LlvmJitVisitor : AstVisitor { if (!isExprIntNZ(expr.right)) { check_divide_by_0(right, expr.at, opType, string(expr.op)) } + if (opType.isSignedInteger) { + let lval = expr.op == "/" || expr.op == "%" ? left : r2v_left + if (expr.op == "%" || expr.op == "%=") { + right = LLVMBuildSelect(g_builder, division_overflow_cond(lval, right, opType), + LLVMConstInt(type_to_llvm_type(opType), 1ul, 0), right, "mod_ovf_to_1") + } else { + check_division_overflow(lval, right, expr.at, opType) + } + } } if (expr.op == "/") { if (opType.isSignedInteger || (opType.isVectorType && opType.vectorBaseType == Type.tInt)) { @@ -7652,6 +7687,10 @@ class public ResolveExternVisitor : AstVisitor { @do_not_delete thisBlock : array uid : UidNodes? + def override canVisitWithAliasSubexpression(expr : ExprAssume?) : bool { + return false + } + def override preVisitFunction(var fun : FunctionPtr) : void { uid.reset(fun) thisFunc = fun @@ -8191,6 +8230,10 @@ class public DisableJitVisitor : AstVisitor { def TypeInfoVisitor { disable = false } + + def override canVisitWithAliasSubexpression(expr : ExprAssume?) : bool { + return false + } // Default-argument inits are inlined into call sites at infer, so the copy left on the // argument is a dead husk LlvmJitVisitor declines too - and ClearUnusedSymbols nulls refs // inside it, so expr.func there is legitimately null. diff --git a/modules/dasLLVM/daslib/llvm_jit_run.das b/modules/dasLLVM/daslib/llvm_jit_run.das index c6ac87cd58..f97d51418d 100644 --- a/modules/dasLLVM/daslib/llvm_jit_run.das +++ b/modules/dasLLVM/daslib/llvm_jit_run.das @@ -36,11 +36,11 @@ var LINK_WHOLE_LIB = false // when true, standalone exe links against the whole // invalidates cached DLLs (e.g. edits to llvm_jit.das, llvm_macro.das, llvm_jit_common.das, // runtime helper ABI, default target triple). Cache filenames fold this in, so a bump // makes every previously written DLL miss the cache on the next run and get GC'd. -let LLVM_JIT_CODEGEN_VERSION : uint64 = 0x56ul // darwin in-memory arm emits no dtor list (0x55: handled types iterate and index through the annotation's jit hooks) +let LLVM_JIT_CODEGEN_VERSION : uint64 = 0x57ul // INT_MIN / -1 and % -1 guards on sdiv/srem (0x56: darwin in-memory arm emits no dtor list) // Read by tests-cpp/small/test_jit_emitter_pin.cpp: FNV-1a64 of the emitter sources // (normalized to LF; file list in the test) -let LLVM_JIT_EMITTER_HASH : uint64 = 0x3140f6c8e0a8738eul +let LLVM_JIT_EMITTER_HASH : uint64 = 0x636a5a780f5ea1a8ul let JIT_FNV_PRIME : uint64 = 1099511628211ul diff --git a/src/ast/ast_generate.cpp b/src/ast/ast_generate.cpp index 3f1a37a7a3..c9e6eae0d5 100644 --- a/src/ast/ast_generate.cpp +++ b/src/ast/ast_generate.cpp @@ -876,6 +876,10 @@ namespace das { } virtual void preVisit ( ExprFor * expr ) override { Visitor::preVisit(expr); + if ( expr->iterators.size() != expr->iteratorVariables.size() ) { + ready = false; + return; + } if ( scopes.size()==0 ) { // only top level for loop for ( size_t i=0; i!=expr->iterators.size(); ++i ) { auto & varName = expr->iterators[i]; @@ -908,14 +912,17 @@ namespace das { expr->name = it->second; } } + public: + bool ready = true; protected: vector scopes; das_hash_map rename; }; - void giveBlockVariablesUniqueNames ( ExpressionPtr expr ) { + bool giveBlockVariablesUniqueNames ( ExpressionPtr expr ) { RenameVar rename; expr->visit(rename); + return rename.ready; } // rename variable @@ -1442,7 +1449,9 @@ namespace das { if ( expr->body->rtti_isBlock() ) { forCopy = static_cast(expr->clone()); bodyBlock = static_cast(forCopy->body); - giveBlockVariablesUniqueNames(forCopy); + if ( !giveBlockVariablesUniqueNames(forCopy) ) { + return nullptr; + } if ( hasFinally ) { // break -> set flag, goto mid (finally runs, flag check -> end, iterator_close runs) // continue -> mid (finally runs, advances iterator, re-checks) diff --git a/src/ast/ast_infer_type.cpp b/src/ast/ast_infer_type.cpp index b66a602d75..aca70f2f51 100644 --- a/src/ast/ast_infer_type.cpp +++ b/src/ast/ast_infer_type.cpp @@ -305,7 +305,23 @@ namespace das { } bool InferTypes::canVisitStructure(Structure *st) { if ( fatalAliasLoop ) return false; - return !st->isTemplate; // we don't do a thing with templates + if ( st->isTemplate ) return false; // we don't do a thing with templates + bool aliasLoop = false; + st->aliases.foreach([&](const TypeDeclPtr & atype) -> bool { + vector visited; + visited.push_back(atype->alias); + if ( isLoop(visited, atype) ) { + aliasLoop = true; + error("alias loop detected: '" + describeType(atype) + "'", "", "", + atype->at, CompilationError::recursion_type_alias); + } + return true; + }); + if ( aliasLoop ) { + fatalAliasLoop = true; + return false; + } + return true; } void InferTypes::preVisit(Structure *that) { Visitor::preVisit(that); @@ -1058,6 +1074,7 @@ namespace das { } } } + if (blk->isClosure) break; } return nullptr; } @@ -5329,6 +5346,8 @@ namespace das { return Visitor::visit(expr); } else if (expr->iteratorVariables.size() != expr->sources.size()) { return Visitor::visit(expr); + } else if (!expr->body) { + return Visitor::visit(expr); } // only topmost // which in case of generator is 2, due to @@ -5339,6 +5358,11 @@ namespace das { uint32_t tf = expr->body->getEvalFlags(); if (tf & EvalFlags::yield) { // only unwrap if it has "yield" auto blk = replaceGeneratorFor(expr, func); + if (!blk) { + error("generator for is not fully inferred yet", "", "", + expr->at, CompilationError::not_resolved_yet_block); + return Visitor::visit(expr); + } scopes.back()->needCollapse = true; reportAstChanged(); return blk; diff --git a/src/ast/ast_infer_type_helper.cpp b/src/ast/ast_infer_type_helper.cpp index 09ec3ce206..8f1879d45d 100644 --- a/src/ast/ast_infer_type_helper.cpp +++ b/src/ast/ast_infer_type_helper.cpp @@ -378,6 +378,14 @@ namespace das { return true; } } + for ( auto & tme : decl->typeMacroExpr ) { + if ( tme && tme->rtti_isTypeDecl() ) { + auto te = static_cast(tme); + if ( te->typeexpr && isLoop(visited, te->typeexpr) ) { + return true; + } + } + } if ( decl->baseType == Type::alias ) { visited.pop_back(); } @@ -389,7 +397,7 @@ namespace das { if (decl->baseType == Type::typeDecl || decl->baseType == Type::typeMacro) { return nullptr; } - if (decl->baseType == Type::autoinfer && !autoToAlias) { // until alias is fully resolved, can't infer + if (decl->baseType == Type::autoinfer && (!autoToAlias || decl->alias.empty())) { return nullptr; } if (decl->baseType == Type::alias || (decl->baseType == Type::autoinfer && autoToAlias)) { @@ -481,6 +489,8 @@ namespace das { resT->firstType = inferAlias(decl->firstType, fptr, aliases, options, autoToAlias); if (!resT->firstType) return nullptr; + if (!resT->firstType->isAutoOrAlias() && !resT->firstType->isTableKeyType()) + return nullptr; } if (decl->secondType) { resT->secondType = inferAlias(decl->secondType, fptr, aliases, options, autoToAlias); diff --git a/tests/aot/test_assume_string_builder.das b/tests/aot/test_assume_string_builder.das new file mode 100644 index 0000000000..95f56b0c72 --- /dev/null +++ b/tests/aot/test_assume_string_builder.das @@ -0,0 +1,14 @@ +options gen2 +options optimize = false +require dastest/testing_boost public + +[export] +def assumed_interpolation : string { + assume v = "n={1 + 1}" + return "{v}" +} + +[test] +def test_assume_string_builder(t : T?) { + t |> equal(assumed_interpolation(), "n=2") +} diff --git a/tests/aot/test_lambda_handled_result.das b/tests/aot/test_lambda_handled_result.das new file mode 100644 index 0000000000..c849beb5b2 --- /dev/null +++ b/tests/aot/test_lambda_handled_result.das @@ -0,0 +1,15 @@ +options gen2 +require UnitTest +require dastest/testing_boost public + +[export] +def make_handled_lambda { + var a = @ { return makeDummy(); } + unsafe { delete a; } +} + +[test] +def test_lambda_handled_result(t : T?) { + make_handled_lambda() + t |> success(true, "lambda with a handled result builds") +} diff --git a/tests/aot/test_ref_make_local.das b/tests/aot/test_ref_make_local.das new file mode 100644 index 0000000000..43455b818f --- /dev/null +++ b/tests/aot/test_ref_make_local.das @@ -0,0 +1,10 @@ +options gen2 +require dastest/testing_boost public + +[export] +def zero_function_ref() => type + +[test] +def test_ref_make_local(t : T?) { + t |> success(zero_function_ref() == null, "zeroed function reference") +} diff --git a/tests/jit_tests/div_overflow.das b/tests/jit_tests/div_overflow.das new file mode 100644 index 0000000000..3e223ee5ef --- /dev/null +++ b/tests/jit_tests/div_overflow.das @@ -0,0 +1,65 @@ +options gen2 +require dastest/testing_boost + +def div_jit(a : int, b : int) { + return a / b +} + +def mod_jit(a : int, b : int) { + return a % b +} + +def div64_jit(a : int64, b : int64) { + return a / b +} + +def mod64_jit(a : int64, b : int64) { + return a % b +} + +def set_div_jit(a : int, b : int) { + var r = a + r /= b + return r +} + +def set_mod_jit(a : int, b : int) { + var r = a + r %= b + return r +} + +def expect_div_panic(t : T?; msg : string; blk : block) { + var failed = false + try { + invoke(blk) + } recover { + failed = true + } + t |> success(failed, msg) +} + +[test] +def test_division_overflow(t : T?) { + t |> run("modulo folds to zero") @@(t : T?) { + t |> equal(mod_jit(INT_MIN, -1), 0, "INT_MIN % -1") + t |> equal(mod64_jit(LONG_MIN, -1l), 0l, "LONG_MIN % -1") + t |> equal(set_mod_jit(INT_MIN, -1), 0, "INT_MIN %= -1") + } + t |> run("division panics") @@(t : T?) { + expect_div_panic(t, "INT_MIN / -1") $ { + print("{div_jit(INT_MIN, -1)}") + } + expect_div_panic(t, "LONG_MIN / -1") $ { + print("{div64_jit(LONG_MIN, -1l)}") + } + expect_div_panic(t, "INT_MIN /= -1") $ { + print("{set_div_jit(INT_MIN, -1)}") + } + } + t |> run("plain values unaffected") @@(t : T?) { + t |> equal(div_jit(-14, 7), -2, "-14 / 7") + t |> equal(mod_jit(-14, 3), -2, "-14 % 3") + t |> equal(div64_jit(-14l, 7l), -2l, "-14 / 7 int64") + } +} diff --git a/tests/language/failed_comprehension_source_in_generator.das b/tests/language/failed_comprehension_source_in_generator.das new file mode 100644 index 0000000000..851d22badc --- /dev/null +++ b/tests/language/failed_comprehension_source_in_generator.das @@ -0,0 +1,4 @@ +options gen2 +expect 30166, 30183, 30192, 30341 + +[export] def a{[iterator for(v in f({for (v in []); (!0)}) => {for (i in (0)); 0; where 0}); {0}]; } diff --git a/tests/language/failed_generator_dim.das b/tests/language/failed_generator_dim.das new file mode 100644 index 0000000000..96b7a48b48 --- /dev/null +++ b/tests/language/failed_generator_dim.das @@ -0,0 +1,11 @@ +options gen2 +expect 30109, 30816 + +variant var9 {} + +var v : var9[generator>> {}] + +[export] +def main { + pass +} diff --git a/tests/language/failed_generator_for_uninferred_rename.das b/tests/language/failed_generator_for_uninferred_rename.das new file mode 100644 index 0000000000..385ef8c47c --- /dev/null +++ b/tests/language/failed_generator_for_uninferred_rename.das @@ -0,0 +1,6 @@ +options gen2 +expect 30113, 30158, 30166, 30192 + +require UnitTest +[export] +def a{[iterator for((r) in "{({for(i in{0=>0});SomeEnum.})}"); 1] as a; } diff --git a/tests/language/failed_goto_out_of_block.das b/tests/language/failed_goto_out_of_block.das new file mode 100644 index 0000000000..2ff56233f3 --- /dev/null +++ b/tests/language/failed_goto_out_of_block.das @@ -0,0 +1,11 @@ +options gen2 +expect 30819 + +[export] +def main { + invoke($() { + goto label 50 + }) + label 50: + pass +} diff --git a/tests/language/failed_struct_alias_loop.das b/tests/language/failed_struct_alias_loop.das new file mode 100644 index 0000000000..47a020e69d --- /dev/null +++ b/tests/language/failed_struct_alias_loop.das @@ -0,0 +1,11 @@ +options gen2 +expect 31106 + +struct S { + typedef T = array +} + +[export] +def main { + pass +} diff --git a/tests/language/failed_struct_alias_typemacro_loop.das b/tests/language/failed_struct_alias_typemacro_loop.das new file mode 100644 index 0000000000..add33b1650 --- /dev/null +++ b/tests/language/failed_struct_alias_typemacro_loop.das @@ -0,0 +1,11 @@ +options gen2 +expect 31106 + +struct S { + typedef T = Unknown +} + +[export] +def main { + pass +} diff --git a/tests/language/failed_table_literal_float16_key.das b/tests/language/failed_table_literal_float16_key.das new file mode 100644 index 0000000000..dc57a2416f --- /dev/null +++ b/tests/language/failed_table_literal_float16_key.das @@ -0,0 +1,7 @@ +options gen2 +expect 30254:2, 30814, 30820 + +[export] +def main { + return {0h => 1} +} diff --git a/tests/language/try_recover_flow.das b/tests/language/try_recover_flow.das new file mode 100644 index 0000000000..02fd7ca81a --- /dev/null +++ b/tests/language/try_recover_flow.das @@ -0,0 +1,236 @@ +options gen2 +require dastest/testing_boost public + +var g_rounds = 5 +var g_void_seen = 0 +var g_refs <- [11, 22] + +[export, no_jit] +def break_in_for : int { + var seen = 0 + for (i in range(0, g_rounds)) { + try { + break if (i == 2) + seen ++ + } recover { + } + seen += 10 + } + return seen +} + +[export, no_jit] +def continue_in_while : int { + var seen = 0 + var i = 0 + while (i < g_rounds) { + i ++ + try { + continue if (i % 2 == 0) + seen ++ + } recover { + } + seen += 10 + } + return seen +} + +[export, no_jit] +def break_in_recover : int { + var seen = 0 + for (i in range(0, g_rounds)) { + try { + panic("stop") if (i == 2) + seen ++ + } recover { + break + } + seen += 10 + } + return seen +} + +[export, no_jit] +def nested_try : int { + var seen = 0 + for (i in range(0, g_rounds)) { + try { + try { + continue if (i == 1) + break if (i == 3) + } recover { + } + seen ++ + } recover { + } + seen += 10 + } + return seen +} + +[export, no_jit] +def inner_loop_break : int { + var seen = 0 + for (i in range(0, g_rounds)) { + try { + for (j in range(0, 4)) { + break if (j == 1) + seen ++ + } + break if (i == 2) + } recover { + } + seen += 10 + } + return seen +} + +[export] +def inner_only_break : int { + var seen = 0 + try { + for (j in range(0, 4)) { + break if (j == 2) + seen ++ + } + seen += 100 + } recover { + } + return seen +} + +[export] +def try_without_exit : int { + var seen = 0 + for (_i in range(0, g_rounds)) { + try { + seen ++ + } recover { + } + } + return seen +} + +[export, no_jit] +def return_in_try : int { + try { + return g_rounds * 2 + } recover { + } + return -1 +} + +[export, no_jit] +def void_return_in_try { + try { + g_void_seen = 1 + return + } recover { + } + g_void_seen = 2 +} + +[export, no_jit] +def return_from_loop_in_try : int { + try { + var s = 0 + for (i in range(0, g_rounds)) { + s += i + return s if (s > 2) + } + } recover { + } + return -1 +} + +[export, no_jit] +def return_from_nested_try : int { + try { + try { + return g_rounds + 4 + } recover { + } + } recover { + } + return -1 +} + +[export, no_jit] +def continue_and_return : int { + var acc = 0 + for (i in range(0, g_rounds)) { + try { + continue if (i == 1) + return acc if (i == 3) + acc += i + } recover { + } + acc += 100 + } + return -1 +} + +[no_jit] +def moved_array : array { + try { + return <- [1, 2, g_rounds] + } recover { + } + var e : array + return <- e +} + +[export] +def return_moved_array : int { + let got <- moved_array() + return length(got) +} + +def call_it(blk : block<(x : int) : int>) : int { + return invoke(blk, 5) +} + +[export, no_jit] +def return_ref_in_try : int & { + try { + unsafe { + return g_refs[0] + } + } recover { + } + unsafe { + return g_refs[1] + } +} + +[export, no_jit] +def block_return_in_try : int { + try { + return call_it() $(x : int) : int { + return x * 2 + } + } recover { + } + return -1 +} + +[test] +def test_try_recover_control_flow(t : T?) { + t |> equal(break_in_for(), 22, "break leaves the for loop") + t |> equal(continue_in_while(), 33, "continue skips the rest of the while iteration") + t |> equal(break_in_recover(), 22, "break in the recover block leaves the loop") + t |> equal(nested_try(), 22, "nested try chains the exit outwards") + t |> equal(inner_loop_break(), 23, "break in a loop inside the try stays inner") + t |> equal(inner_only_break(), 102, "a loop break with no loop around the try needs no exit") + t |> equal(try_without_exit(), 5, "try with no exit is unaffected") + t |> equal(return_in_try(), 10, "return carries its value out") + void_return_in_try() + t |> equal(g_void_seen, 1, "a void return needs no slot") + t |> equal(return_from_loop_in_try(), 3, "return crosses a loop inside the try") + t |> equal(return_from_nested_try(), 9, "nested try shares the return slot") + t |> equal(continue_and_return(), 202, "one try with continue and return") + t |> equal(return_moved_array(), 3, "moved array survives the return slot") + t |> equal(block_return_in_try(), 10, "a block return is not the function's") + return_ref_in_try() = 99 + t |> equal(g_refs[0], 99, "a returned reference rides the slot as a pointer to the same storage") +} diff --git a/tests/math/inf_and_nan.das b/tests/math/inf_and_nan.das index 79a0ec7982..37c0f9481e 100644 --- a/tests/math/inf_and_nan.das +++ b/tests/math/inf_and_nan.das @@ -44,6 +44,36 @@ def geq(a, b) { return a >= b } +[sideeffects] +def opaque(x) { + return x +} + +[test] +def test_half_subnormals_survive_conversion(t : T?) { + t |> equal(float(half(opaque(3.0517578e-5f))), 3.0517578e-5f) + t |> equal(float(half(opaque(5.9604645e-8f))), 5.9604645e-8f) // min subnormal + t |> equal(float(half(opaque(6.097555e-5f))), 6.097555e-5f) // max subnormal + t |> equal(float(half(opaque(2.9802322e-8f))), 0.0f) // half of min: to zero +} + +[test] +def test_abs_clears_the_sign_of_zero(t : T?) { + t |> run("folded") @@(t : T?) { + t |> equal(unsafe(reinterpret(abs(-0.0f))), 0u) + t |> equal(unsafe(reinterpret(abs(-0.0lf))), 0ul) + t |> equal(unsafe(reinterpret(abs(float4(-0.0f, -0.0f, -0.0f, -0.0f)))), uint4(0u, 0u, 0u, 0u)) + } + t |> run("runtime") @@(t : T?) { + t |> equal(unsafe(reinterpret(abs(opaque(-0.0f)))), 0u) + t |> equal(unsafe(reinterpret(abs(opaque(-0.0lf)))), 0ul) + let mz4 = float4(opaque(-0.0f), opaque(-0.0f), opaque(-0.0f), opaque(-0.0f)) + t |> equal(unsafe(reinterpret(abs(mz4))), uint4(0u, 0u, 0u, 0u)) + let mz2 = float2(opaque(-0.0f), opaque(-0.0f)) + t |> equal(unsafe(reinterpret(abs(mz2))), uint2(0u, 0u)) + } +} + [test] def test_inf_and_nan(t : T?) { if (host_folds_non_finite()) {