diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h b/tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h index 029498ab2..679a0878e 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h @@ -63,6 +63,22 @@ class MLIRPrinter printAttribute(out, id, true); } + // a C variadic function type (`@varargs`) reads back as `CVarArgs<(...) => ...>`; an arrow + // type alone would read back as a TypeScript function, called another way + template + void printCVarArgsOrFuncType(T &out, F t) + { + if (t.isCVarArg()) + { + out << "CVarArgs<"; + printFuncType(out, t); + out << ">"; + return; + } + + printFuncType(out, t); + } + template void printFuncType(T &out, F t) { @@ -223,7 +239,10 @@ class MLIRPrinter // it would parse back as HybridFunctionType instead - a different, // wider (two-pointer-struct vs single-pointer) storage layout that // silently misaligns every subsequent read through the field. - if (allowMethodSignature && field.id && isa(field.type)) + // A C variadic function field is a value, not a method: it prints as `CVarArgs<...>`, + // which reads back as the same one-pointer type. + if (allowMethodSignature && field.id && isa(field.type) + && !mlir::cast(field.type).isCVarArg()) { printFieldName(out, field.id); printFuncTypeAsMethodSignature(out, mlir::cast(field.type)); @@ -408,10 +427,10 @@ class MLIRPrinter out << t.getName().getValue().str().c_str(); }) .template Case([&](auto t) { - printFuncType(out, t); + printCVarArgsOrFuncType(out, t); }) .template Case([&](auto t) { - printFuncType(out, t); + printCVarArgsOrFuncType(out, t); }) .template Case([&](auto t) { out << "new "; diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRTypeHelper.h b/tslang/include/TypeScript/MLIRLogic/MLIRTypeHelper.h index bcb0a8318..72755b81d 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRTypeHelper.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRTypeHelper.h @@ -910,6 +910,23 @@ class MLIRTypeHelper return isVarArg; } + // a C variadic function (`@varargs`), held as a function or a hybrid function + bool getCVarArgFromFuncRef(mlir::Type funcType) + { + funcType = stripOptionalType(funcType); + if (auto func = dyn_cast(funcType)) + { + return func.isCVarArg(); + } + + if (auto hybridFunc = dyn_cast(funcType)) + { + return hybridFunc.isCVarArg(); + } + + return false; + } + mlir::Type getOmitThisFunctionTypeFromFuncRef(mlir::Type funcType) { mlir::Type paramsType; @@ -1806,6 +1823,13 @@ class MLIRTypeHelper auto leftIsVarArg = getVarArgFromFuncRef(typeLeft); auto rightIsVarArg = getVarArgFromFuncRef(typeRight); + // a C variadic function and a TypeScript one are called differently: no common type + auto isCVarArg = getCVarArgFromFuncRef(typeLeft); + if (isCVarArg != getCVarArgFromFuncRef(typeRight)) + { + return mlir::Type(); + } + auto hybridFuncIsNeeded = false; if (!isLeftTypeFunc) { @@ -1844,7 +1868,7 @@ class MLIRTypeHelper return typeLeft; } - return mlir_ts::HybridFunctionType::get(context, leftInputs, leftResults, leftIsVarArg); + return mlir_ts::HybridFunctionType::get(context, leftInputs, leftResults, leftIsVarArg, isCVarArg); } return mlir::Type(); diff --git a/tslang/include/TypeScript/TypeScriptTypes.td b/tslang/include/TypeScript/TypeScriptTypes.td index 3120cfec0..21288e8e9 100644 --- a/tslang/include/TypeScript/TypeScriptTypes.td +++ b/tslang/include/TypeScript/TypeScriptTypes.td @@ -764,24 +764,33 @@ def TypeScript_Function : TypeScript_Type<"Function"> { let summary = "Map from a list of inputs to a list of results"; let description = [{ }]; - let parameters = (ins ArrayRefParameter<"Type", "type">:$inputs, ArrayRefParameter<"Type", "type">:$results, "bool":$isVarArg); + // `isVarArg`: the last parameter is a TypeScript rest parameter, which takes the extra + // arguments packed into an array. `isCVarArg`: a C variadic function, declared + // `@varargs declare function printf(format: string)`; its extra arguments go to C's `...`, so + // a call through a value of this type is a variadic call. Printed only when set. + let parameters = (ins ArrayRefParameter<"Type", "type">:$inputs, ArrayRefParameter<"Type", "type">:$results, "bool":$isVarArg, + DefaultValuedParameter<"bool", "false">:$isCVarArg); let assemblyFormat = "`<` params `>`"; let builders = [ - TypeBuilder<(ins CArg<"ArrayRef", "{}">:$inputs, CArg<"ArrayRef", "{}">:$results, CArg<"bool", "false">:$isVarArg), [{ - return Base::get($_ctxt, inputs, results, isVarArg); + TypeBuilder<(ins CArg<"ArrayRef", "{}">:$inputs, CArg<"ArrayRef", "{}">:$results, CArg<"bool", "false">:$isVarArg, + CArg<"bool", "false">:$isCVarArg), [{ + return Base::get($_ctxt, inputs, results, isVarArg, isCVarArg); }]>, TypeBuilder<(ins "mlir::FunctionType":$funcType, CArg<"bool", "false">:$isVarArg), [{ - return Base::get(funcType.getContext(), funcType.getInputs(), funcType.getResults(), isVarArg); + return Base::get(funcType.getContext(), funcType.getInputs(), funcType.getResults(), isVarArg, false); }]> ]; - - let skipDefaultBuilders = 1; + + let skipDefaultBuilders = 1; //let genStorageClass = 0; let extraClassDeclaration = [{ bool isVarArg() const; + /// A C variadic function (`@varargs`): the extra arguments of a call go to C's `...`. + bool isCVarArg() const { return getIsCVarArg(); } + /// Returns the result type of the function. Type getReturnType(); @@ -826,24 +835,31 @@ def TypeScript_HybridFunction : TypeScript_Type<"HybridFunction"> { let summary = "Map from a list of inputs to a list of results with/without 'this' reference"; let description = [{ }]; - let parameters = (ins ArrayRefParameter<"Type", "type">:$inputs, ArrayRefParameter<"Type", "type">:$results, "bool":$isVarArg); + // `isCVarArg`: as for TypeScript_Function - a C variadic function, which a hybrid value holds + // with no `this`. Printed only when set. + let parameters = (ins ArrayRefParameter<"Type", "type">:$inputs, ArrayRefParameter<"Type", "type">:$results, "bool":$isVarArg, + DefaultValuedParameter<"bool", "false">:$isCVarArg); let assemblyFormat = "`<` params `>`"; let builders = [ - TypeBuilder<(ins "ArrayRef":$inputs, "ArrayRef":$results, "bool":$isVarArg), [{ - return Base::get($_ctxt, inputs, results, isVarArg); + TypeBuilder<(ins "ArrayRef":$inputs, "ArrayRef":$results, "bool":$isVarArg, CArg<"bool", "false">:$isCVarArg), [{ + return Base::get($_ctxt, inputs, results, isVarArg, isCVarArg); }]>, TypeBuilder<(ins "FunctionType":$funcType), [{ - return Base::get(funcType.getContext(), funcType.getInputs(), funcType.getResults(), funcType.isVarArg()); + return Base::get(funcType.getContext(), funcType.getInputs(), funcType.getResults(), funcType.isVarArg(), + funcType.isCVarArg()); }]> - ]; + ]; - let skipDefaultBuilders = 1; + let skipDefaultBuilders = 1; //let genStorageClass = 0; let extraClassDeclaration = [{ bool isVarArg() const; + /// A C variadic function (`@varargs`): the extra arguments of a call go to C's `...`. + bool isCVarArg() const { return getIsCVarArg(); } + /// Input types. unsigned getNumInputs() const; Type getInput(unsigned i) const { return getInputs()[i]; } diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index 402216cef..4fd934b13 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -2111,7 +2111,8 @@ struct SymbolCallInternalOpLowering : public TsLlvmPattern(funcOp)) { auto tsFuncType = tsFuncOp.getFunctionType(); - llvmFuncType = tch.convertFunctionSignature(op.getContext(), tsFuncType.getResults(), tsFuncType.getInputs(), tsFuncType.isVarArg()); + llvmFuncType = tch.convertFunctionSignature(op.getContext(), tsFuncType.getResults(), tsFuncType.getInputs(), + tsFuncType.isVarArg() || tsFuncType.isCVarArg()); } if (!llvmFuncType) @@ -2383,6 +2384,39 @@ static mlir::Value recordFromRegisters(Operation *op, ConversionPatternRewriter return rewriter.create(op->getLoc(), abi.recordResult, slot); } +// An indirect call of a C variadic function (`@varargs`, the callee's !ts.func or +// !ts.hybrid_func says so) is a variadic call: `var_callee_type` names the fixed parameters, and +// the arguments after them go to `...` as the C ABI passes those (on Win64 a double goes in both +// its XMM and its integer register). Without it LLVM passes every argument as a fixed one, where +// printf does not look. +template +static void markCVarArgCall(CallOpTy callOp, mlir::Type calleeType, mlir::ValueRange args, mlir::TypeRange llvmResults) +{ + auto fixedCount = 0u; + if (auto funcType = dyn_cast(calleeType); funcType && funcType.isCVarArg()) + { + fixedCount = funcType.getNumInputs(); + } + else if (auto hybridFuncType = dyn_cast(calleeType); hybridFuncType && hybridFuncType.isCVarArg()) + { + fixedCount = hybridFuncType.getNumInputs(); + } + else + { + return; + } + + SmallVector params; + for (auto arg : args.take_front(fixedCount)) + { + params.push_back(arg.getType()); + } + + auto *context = callOp->getContext(); + auto result = llvmResults.empty() ? LLVM::LLVMVoidType::get(context) : llvmResults.front(); + callOp.setVarCalleeTypeAttr(mlir::TypeAttr::get(LLVM::LLVMFunctionType::get(result, params, /*isVarArg=*/true))); +} + struct CallInternalOpLowering : public TsLlvmPattern { using TsLlvmPattern::TsLlvmPattern; @@ -2439,6 +2473,7 @@ struct CallInternalOpLowering : public TsLlvmPattern // generic (TypeRange, ValueRange) builder above does not set it. callRes.getProperties().setOperandSegmentSizes({static_cast(callOperands.size()), 0}); callRes.setOpBundleSizes({}); + markCVarArgCall(callRes, op.getOperand(0).getType(), abi.args, llvmTypes); if (abi.anyArgAttr) { callRes.setArgAttrsAttr(mlir::ArrayAttr::get(rewriter.getContext(), abi.argAttrs)); @@ -2549,6 +2584,8 @@ struct CallHybridInternalOpLowering : public TsLlvmPattern(loc, llvmTypes, ops); callRes.getProperties().setOperandSegmentSizes({static_cast(ops.size()), 0}); callRes.setOpBundleSizes({}); + // a C variadic function is held with no `this`, so it is called here + markCVarArgCall(callRes, hybridFuncType, mlir::ValueRange(ops).drop_front(), llvmTypes); return callRes.getResults(); }); } @@ -2626,6 +2663,7 @@ struct InvokeOpLowering : public TsLlvmPattern auto invokeOp = rewriter.create(op->getLoc(), llvmTypes, mlir::FlatSymbolRefAttr(), operands, op.getNormalDest(), transformed.getNormalDestOperands(), op.getUnwindDest(), transformed.getUnwindDestOperands()); + markCVarArgCall(invokeOp, op.getCallOperands().front().getType(), abi.args, llvmTypes); if (abi.anyArgAttr) { invokeOp.setArgAttrsAttr(mlir::ArrayAttr::get(rewriter.getContext(), abi.argAttrs)); @@ -2738,6 +2776,8 @@ struct InvokeHybridOpLowering : public TsLlvmPattern auto callRes = rewriter.create(loc, TypeRange(llvmTypes), mlir::FlatSymbolRefAttr(), ops, continuationBlock, transformed.getNormalDestOperands(), op.getUnwindDest(), transformed.getUnwindDestOperands()); + // a C variadic function is held with no `this`, so it is called here + markCVarArgCall(callRes, hybridFuncType, mlir::ValueRange(ops).drop_front(), llvmTypes); rewriter.setInsertionPointToStart(continuationBlock); diff --git a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp index c56ca9c57..5b39e1940 100644 --- a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp +++ b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp @@ -2135,13 +2135,13 @@ namespace mlirgen slotOperands.append(operands.begin(), operands.end()); callAttrs.push_back(builder.getNamedAttr(SRET_ARG_ATTR_NAME, builder.getI32IntegerAttr(0))); callResult = mlirGenCallFunction(location, swappedFuncType, swappedFuncRefValue, slotOperands, genContext, - callAttrs); + callAttrs, /*hiddenOperandsCount=*/2); } else { slotOperands.append(operands.begin(), operands.end()); callResult = mlirGenCallFunction(location, calledFuncType, unboundFuncRefValue, thisValue, slotOperands, genContext, - callAttrs); + callAttrs, /*hiddenOperandsCount=*/1); } if (mlir::failed(callResult)) diff --git a/tslang/lib/TypeScript/MLIRGenCast.cpp b/tslang/lib/TypeScript/MLIRGenCast.cpp index 0ae05704b..b979d6b57 100644 --- a/tslang/lib/TypeScript/MLIRGenCast.cpp +++ b/tslang/lib/TypeScript/MLIRGenCast.cpp @@ -1686,7 +1686,8 @@ namespace mlirgen { auto funcValue = builder.create( location, - mlir_ts::FunctionType::get(builder.getContext(), hybridFuncType.getInputs(), hybridFuncType.getResults(), hybridFuncType.isVarArg()), + mlir_ts::FunctionType::get(builder.getContext(), hybridFuncType.getInputs(), hybridFuncType.getResults(), + hybridFuncType.isVarArg(), hybridFuncType.isCVarArg()), value); return V(builder.create(location, type, funcValue)); } @@ -1748,6 +1749,16 @@ namespace mlirgen EXIT_IF_FAILED(result); } + // A C variadic function is called with C's variadic convention and a TypeScript + // function is not, so neither stands in for the other: through a value of the wrong + // kind the extra arguments would land in the wrong registers. + if (isCVarArgFunctionType(valueType) != isCVarArgFunctionType(type)) + { + emitError(location, "can't assign '") + << to_print(valueType) << "' to '" << to_print(type) << "': only one of them is a C variadic function"; + return mlir::failure(); + } + // fall through to finish cast operation if (!mth.CanCastFunctionTypeToFunctionType(valueType, type)) { diff --git a/tslang/lib/TypeScript/MLIRGenFunctions.cpp b/tslang/lib/TypeScript/MLIRGenFunctions.cpp index a2c779faf..71b2734b3 100644 --- a/tslang/lib/TypeScript/MLIRGenFunctions.cpp +++ b/tslang/lib/TypeScript/MLIRGenFunctions.cpp @@ -578,6 +578,17 @@ namespace mlirgen auto dllExport = processFunctionAttributes(location, fullName, functionLikeDeclarationBaseAST, attrs, funcProtoGenContext, suppressExportForGenericInstantiation); + // A C variadic function (`@varargs`) says so in its type, so a call through a value of + // it - `let p = printf`, a `typeof printf` parameter - is a variadic call too. The type + // comes here from several places (discovered, cached, declared), so it is set here. + if (funcType && !funcType.isCVarArg() && llvm::any_of(attrs, [](mlir::NamedAttribute &attr) { + return attr.getName() == "func.varargs"; + })) + { + funcType = mlir_ts::FunctionType::get(builder.getContext(), funcType.getInputs(), funcType.getResults(), + funcType.isVarArg(), /*isCVarArg=*/true); + } + if (funcType) { auto it = getCaptureVarsMap().find(funcProto->getName()); diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index bdc88533d..8de992caa 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -2151,8 +2151,9 @@ class MLIRGenImpl auto actualType = variableDeclarationInfo.typeProvided == TypeProvided::Yes ? type : mth.wideStorageType(type); - // this is 'let', if 'let' is func, it should be HybridFunction - if (auto funcType = dyn_cast(actualType)) + // this is 'let', if 'let' is func, it should be HybridFunction. Not a C variadic + // function: it is one pointer, as in C (see getCVarArgsType). + if (auto funcType = dyn_cast(actualType); funcType && !funcType.isCVarArg()) { actualType = mlir_ts::HybridFunctionType::get(builder.getContext(), funcType); } @@ -2210,10 +2211,12 @@ class MLIRGenImpl // (adjustLocalVariableType): as a hybrid function. Its declaration in __decls, // `let f : (p0: number) => number`, reads back as one, so a library inferring a plain // function from its initializer stored a smaller value than its importers loaded - the - // call's arguments landed in the wrong place and it returned garbage. + // call's arguments landed in the wrong place and it returned garbage. A C variadic + // function stays one pointer: its declaration, `let f : CVarArgs<(p0: string) => s32>`, + // reads back as one (getCVarArgsType). if (variableDeclarationInfo.isExport || variableDeclarationInfo.isImport || variableDeclarationInfo.isExternal) { - if (auto funcType = dyn_cast(actualType)) + if (auto funcType = dyn_cast(actualType); funcType && !funcType.isCVarArg()) { actualType = mlir_ts::HybridFunctionType::get(builder.getContext(), funcType); } @@ -8685,30 +8688,33 @@ class MLIRGenImpl template ValueOrLogicalResult mlirGenCallFunction(mlir::Location location, T calledFuncType, mlir::Value funcRefValue, SmallVector &operands, const GenContext &genContext, - ArrayRef callAttrs = {}) + ArrayRef callAttrs = {}, int hiddenOperandsCount = 0) { - return mlirGenCallFunction(location, calledFuncType, funcRefValue, mlir::Value(), operands, genContext, callAttrs); + return mlirGenCallFunction(location, calledFuncType, funcRefValue, mlir::Value(), operands, genContext, callAttrs, + hiddenOperandsCount); } // `callAttrs` go on the call (CallIndirectOp): CPP_CALL_ATTR_NAME, SRET_ARG_ATTR_NAME + // `hiddenOperandsCount`: the leading `operands` the user did not write either (a @cpp method's + // result slot, and its `this` when that is passed after the slot) template ValueOrLogicalResult mlirGenCallFunction(mlir::Location location, T calledFuncType, mlir::Value funcRefValue, mlir::Value thisValue, SmallVector &operands, - const GenContext &genContext, ArrayRef callAttrs = {}) + const GenContext &genContext, ArrayRef callAttrs = {}, + int hiddenOperandsCount = 0) { // `thisValue` is the one operand a call gets that the user did not write: the object of // a method or constructor call (a bound function), the capture of a closure (also a // bound function) or the receiver of an extension function. Callees reached without // it take only what the user wrote. - auto hiddenOperandsCount = 0; if (thisValue) { operands.insert(operands.begin(), thisValue); - hiddenOperandsCount = 1; + hiddenOperandsCount++; } if (mlir::failed(mlirGenPrepareCallOperands(location, operands, calledFuncType.getInputs(), calledFuncType.isVarArg(), - hiddenOperandsCount, genContext))) + hiddenOperandsCount, isCVarArgFunctionType(calledFuncType), genContext))) { return mlir::failure(); } @@ -8827,17 +8833,27 @@ class MLIRGenImpl return funcOp && funcOp.getBody().empty(); } - // A call with fewer arguments than the callee needs is TypeScript's TS2554. Only a parameter - // whose type admits `undefined` itself may be left out - an optional (`b?: T`) or a - // default-valued (`b = 5`) one, both typed !ts.optional, and an `undefined` or `void` one. - // A rest parameter is never required. Any other missing parameter is an error; padding it - // with undef would hand the callee garbage. + // A C variadic function, declared `@varargs declare function f(n: s32)` (tsbindgen writes + // them so): its arguments after the declared ones go to C's `...`. Its type says so, whether + // it is called by name or through a value of it (a function or a hybrid function). + bool isCVarArgFunctionType(mlir::Type type) + { + return mth.getCVarArgFromFuncRef(type); + } + + // A call with fewer arguments than the callee needs, or more than it takes, is TypeScript's + // TS2554. Only a parameter whose type admits `undefined` itself may be left out - an optional + // (`b?: T`) or a default-valued (`b = 5`) one, both typed !ts.optional, and an `undefined` or + // `void` one. A rest parameter is never required and takes any number of arguments. Any other + // missing parameter is an error; padding it with undef would hand the callee garbage. An + // extra argument would be passed in a slot the callee does not have. // // `operandsCount` and the indices of `argFuncTypes` include the `hiddenOperandsCount` - // leading operands the compiler inserted (`this`, a closure's capture); the message counts - // only what the user wrote. Too many arguments is not checked here. + // leading operands the compiler inserted (`this`, a closure's capture, a @cpp method's + // result slot); the message counts only what the user wrote. A C variadic callee + // (`isCVarArg`, isCVarArgFunctionType) takes any number of arguments after its declared ones. mlir::LogicalResult checkCallArity(mlir::Location location, int operandsCount, mlir::ArrayRef argFuncTypes, - bool isVarArg, int hiddenOperandsCount) + bool isVarArg, int hiddenOperandsCount, bool isCVarArg) { auto isOmittable = [](mlir::Type type) { return isa(type); @@ -8846,17 +8862,17 @@ class MLIRGenImpl // the parameters before the rest parameter, if any int paramsEnd = argFuncTypes.size() - (isVarArg ? 1 : 0); - auto missingRequired = false; + auto wrongCount = !isVarArg && !isCVarArg && operandsCount > paramsEnd; for (auto i = operandsCount; i < paramsEnd; i++) { if (!isOmittable(argFuncTypes[i])) { - missingRequired = true; + wrongCount = true; break; } } - if (!missingRequired) + if (!wrongCount) { return mlir::success(); } @@ -8892,25 +8908,64 @@ class MLIRGenImpl return mlir::failure(); } + // C's default argument promotions, which a caller applies to what it passes to `...`: a float + // narrower than a double is passed as a double, an integer narrower than an int (and a + // boolean) as an int. printf reads `%f` as a double; an f32 passed as is printed 0.000000. + mlir::LogicalResult promoteCVarArgOperands(mlir::Location location, SmallVector &operands, + int fixedOperandsCount, const GenContext &genContext) + { + for (auto i = fixedOperandsCount; i < (int)operands.size(); i++) + { + auto type = mth.stripLiteralType(operands[i].getType()); + mlir::Type promotedType; + if (auto floatType = dyn_cast(type); floatType && floatType.getWidth() < 64) + { + promotedType = getNumberType(); + } + else if (auto intType = dyn_cast(type); intType && intType.getWidth() < 32) + { + promotedType = builder.getIntegerType(32, intType.getSignedness()); + } + else if (isa(type)) + { + promotedType = builder.getIntegerType(32, mlir::IntegerType::Signed); + } + + if (promotedType) + { + auto result = cast(location, promotedType, operands[i], genContext); + EXIT_IF_FAILED_OR_NO_VALUE(result) + operands[i] = V(result); + } + } + + return mlir::success(); + } + mlir::LogicalResult mlirGenPrepareCallOperands(mlir::Location location, SmallVector &operands, mlir::ArrayRef argFuncTypes, bool isVarArg, - int hiddenOperandsCount, const GenContext &genContext) + int hiddenOperandsCount, bool isCVarArg, const GenContext &genContext) { int opArgsCount = operands.size(); int funcArgsCount = argFuncTypes.size(); + if (mlir::failed(checkCallArity(location, opArgsCount, argFuncTypes, isVarArg, hiddenOperandsCount, isCVarArg))) + { + return mlir::failure(); + } + if (mlir::failed(mlirGenAdjustOperandTypes(location, operands, argFuncTypes, isVarArg, genContext))) { return mlir::failure(); } - if (funcArgsCount > opArgsCount) + if (isCVarArg && mlir::failed(promoteCVarArgOperands(location, operands, funcArgsCount, genContext))) { - if (mlir::failed(checkCallArity(location, opArgsCount, argFuncTypes, isVarArg, hiddenOperandsCount))) - { - return mlir::failure(); - } + return mlir::failure(); + } + if (funcArgsCount > opArgsCount) + { auto lastArgIndex = argFuncTypes.size() - 1; // -1 to exclude count params @@ -13155,6 +13210,8 @@ class MLIRGenImpl mlir_ts::ExtensionFunctionType getExtensionFunctionType(mlir_ts::FunctionType funcType); + mlir::Type getCVarArgsType(mlir::Type type); + mlir::Type getSignature(SignatureDeclarationBase signature, const GenContext &genContext); mlir::Type getFunctionType(SignatureDeclarationBase signature, const GenContext &genContext); diff --git a/tslang/lib/TypeScript/MLIRGenTypes.cpp b/tslang/lib/TypeScript/MLIRGenTypes.cpp index 524501b66..9f1bff46d 100644 --- a/tslang/lib/TypeScript/MLIRGenTypes.cpp +++ b/tslang/lib/TypeScript/MLIRGenTypes.cpp @@ -924,6 +924,7 @@ namespace mlirgen {"Ref", true }, // alias of Reference {"BoxedObject", true }, // an object held by reference, see MLIRPrinter::printBoxedObjectTypes {"Shared", true }, // a counted handle, own spec 23 + {"CVarArgs", true }, // a C variadic function type, see getCVarArgsType {"Readonly", true }, {"Partial", true }, {"Required", true }, @@ -1014,6 +1015,7 @@ namespace mlirgen {"Ref", true }, // alias of Reference {"BoxedObject", true }, // an object held by reference, see MLIRPrinter::printBoxedObjectTypes {"Shared", true }, // a counted handle, own spec 23 + {"CVarArgs", true }, // a C variadic function type, see getCVarArgsType {"ThisType", true }, //{"Array", true } }; @@ -1165,7 +1167,7 @@ namespace mlirgen { enum class EmbeddedType { - None, TypeOf, Reference, BoxedObject, Shared, FirstTypeArgument, NonNullable, Array, ReadonlyArray, ReturnType, + None, TypeOf, Reference, BoxedObject, Shared, CVarArgs, FirstTypeArgument, NonNullable, Array, ReadonlyArray, ReturnType, Parameters, ThisParameterType, OmitThisParameter, Uppercase, Lowercase, Capitalize, Uncapitalize }; @@ -1174,6 +1176,7 @@ namespace mlirgen .Cases("Reference", "Ref", EmbeddedType::Reference) .Case("BoxedObject", EmbeddedType::BoxedObject) .Case("Shared", EmbeddedType::Shared) + .Case("CVarArgs", EmbeddedType::CVarArgs) .Cases("Readonly", "Partial", "Required", "ThisType", EmbeddedType::FirstTypeArgument) .Case("NonNullable", EmbeddedType::NonNullable) #ifdef ARRAY_TYPE_AS_ARRAY_CLASS @@ -1211,6 +1214,8 @@ namespace mlirgen return getObjectType(type); case EmbeddedType::Shared: return mlir_ts::SharedType::get(type); + case EmbeddedType::CVarArgs: + return getCVarArgsType(type); case EmbeddedType::FirstTypeArgument: return type; case EmbeddedType::NonNullable: @@ -1267,7 +1272,7 @@ namespace mlirgen { enum class EmbeddedType { - None, TypeOf, Reference, BoxedObject, Shared, ThisType, Array + None, TypeOf, Reference, BoxedObject, Shared, CVarArgs, ThisType, Array }; auto kind = llvm::StringSwitch(name) @@ -1275,6 +1280,7 @@ namespace mlirgen .Cases("Reference", "Ref", EmbeddedType::Reference) .Case("BoxedObject", EmbeddedType::BoxedObject) .Case("Shared", EmbeddedType::Shared) + .Case("CVarArgs", EmbeddedType::CVarArgs) .Case("ThisType", EmbeddedType::ThisType) #ifdef ARRAY_TYPE_AS_ARRAY_CLASS .Case("Array", EmbeddedType::Array) @@ -1298,6 +1304,8 @@ namespace mlirgen case EmbeddedType::Shared: // the type argument is not checked above in this variant return type ? mlir_ts::SharedType::get(type) : mlir::Type(); + case EmbeddedType::CVarArgs: + return type ? getCVarArgsType(type) : mlir::Type(); case EmbeddedType::ThisType: return type; #ifdef ARRAY_TYPE_AS_ARRAY_CLASS @@ -2877,6 +2885,29 @@ namespace mlirgen return mlir_ts::FunctionType::get(builder.getContext(), inputs, results, isVarArg); } + // `CVarArgs<(format: string) => s32>`: the type of a C variadic function, as + // `@varargs declare function printf(format: string): s32` has it - how a declaration names + // one (__decls prints it so, see MLIRPrinter). It is a plain function (one pointer, as in C) + // even where the arrow type alone would be a hybrid one: C code has no `this` to pass, and an + // importer must store it the way its library does. Not a function type: no type. + mlir::Type MLIRGenImpl::getCVarArgsType(mlir::Type type) + { + if (auto funcType = dyn_cast(type)) + { + return mlir_ts::FunctionType::get(builder.getContext(), funcType.getInputs(), funcType.getResults(), + funcType.isVarArg(), /*isCVarArg=*/true); + } + + if (auto hybridFuncType = dyn_cast(type)) + { + return mlir_ts::FunctionType::get(builder.getContext(), hybridFuncType.getInputs(), + hybridFuncType.getResults(), hybridFuncType.isVarArg(), + /*isCVarArg=*/true); + } + + return mlir::Type(); + } + mlir_ts::ExtensionFunctionType MLIRGenImpl::getExtensionFunctionType(mlir_ts::FunctionType funcType) { return mlir_ts::ExtensionFunctionType::get(builder.getContext(), funcType); diff --git a/tslang/lib/TypeScript/TypeScriptOps.cpp b/tslang/lib/TypeScript/TypeScriptOps.cpp index f5140f598..dcfa872c8 100644 --- a/tslang/lib/TypeScript/TypeScriptOps.cpp +++ b/tslang/lib/TypeScript/TypeScriptOps.cpp @@ -183,7 +183,7 @@ mlir_ts::FunctionType mlir_ts::FunctionType::clone(mlir::TypeRange inputs, mlir: clonedResults.push_back(type); } - return get(getContext(), clonedInputs, clonedResults, isVarArg()); + return get(getContext(), clonedInputs, clonedResults, isVarArg(), isCVarArg()); } /// Returns a new function type with the specified arguments and results diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index d8ae1e3f8..00b2b2b38 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -305,6 +305,7 @@ tslang_add_test(NAME test-compile-00-funcs-nesting COMMAND test-runner "${PROJEC tslang_add_test(NAME test-compile-00-funcs-nesting-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_nesting_generic.ts") tslang_add_test(NAME test-compile-00-funcs-nesting-capture COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_nesting_capture.ts") tslang_add_test(NAME test-compile-00-funcs-hybrid-null-this COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_hybrid_null_this.ts") +tslang_add_test(NAME test-compile-00-funcs-c-varargs-storage COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_c_varargs_storage.ts") tslang_add_test(NAME test-compile-00-typeof-static-fold COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold.ts") tslang_add_test(NAME test-compile-00-typeof-function-narrowing COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_function_narrowing.ts") tslang_add_test(NAME test-compile-00-typeof-static-fold-conditions COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold_conditions.ts") @@ -857,6 +858,7 @@ tslang_add_test(NAME test-jit-00-funcs-nesting COMMAND test-runner -jit "${PROJE tslang_add_test(NAME test-jit-00-funcs-nesting-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_nesting_generic.ts") tslang_add_test(NAME test-jit-00-funcs-nesting-capture COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_nesting_capture.ts") tslang_add_test(NAME test-jit-00-funcs-hybrid-null-this COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_hybrid_null_this.ts") +tslang_add_test(NAME test-jit-00-funcs-c-varargs-storage COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_c_varargs_storage.ts") tslang_add_test(NAME test-jit-00-typeof-static-fold COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold.ts") tslang_add_test(NAME test-jit-00-typeof-function-narrowing COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_function_narrowing.ts") tslang_add_test(NAME test-jit-00-typeof-static-fold-conditions COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold_conditions.ts") @@ -1383,6 +1385,7 @@ tslang_add_test(NAME test-compile-export-import-declare-linkname COMMAND test-ru tslang_add_test(NAME test-compile-export-import-owned-returns COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_owned_returns.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_owned_returns.ts") tslang_add_test(NAME test-compile-export-import-const-locals COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_locals.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_locals.ts") tslang_add_test(NAME test-compile-export-import-const-functions COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_functions.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_functions.ts") +tslang_add_test(NAME test-compile-export-import-c-varargs COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_c_varargs.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_c_varargs.ts") tslang_add_test(NAME test-compile-export-import-generators COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_generators.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_generators.ts") # `import './export_os_suffix'` takes the target's file, export_os_suffix.win32.ts or # export_os_suffix.linux.ts: the host's is compiled and linked beside it, and the import's @@ -1491,6 +1494,7 @@ tslang_add_test(NAME test-compile-shared-export-import-owned-returns COMMAND tes tslang_add_test(NAME test-compile-shared-export-import-gc-single-collector COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_gc_single_collector.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_gc_single_collector.ts") tslang_add_test(NAME test-compile-shared-export-import-const-locals COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_locals.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_locals.ts") tslang_add_test(NAME test-compile-shared-export-import-const-functions COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_functions.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_functions.ts") +tslang_add_test(NAME test-compile-shared-export-import-c-varargs COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_c_varargs.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_c_varargs.ts") tslang_add_test(NAME test-compile-shared-export-import-generators COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_generators.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_generators.ts") tslang_add_test(NAME test-compile-shared-export-import-class-extends COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends.ts") tslang_add_test(NAME test-compile-shared-export-import-throw-class COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_throw_class.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_throw_class.ts") @@ -1602,6 +1606,7 @@ tslang_add_test(NAME test-jit-shared-export-import-owned-returns COMMAND test-ru tslang_add_test(NAME test-jit-shared-export-import-gc-single-collector COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_gc_single_collector.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_gc_single_collector.ts") tslang_add_test(NAME test-jit-shared-export-import-const-locals COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_locals.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_locals.ts") tslang_add_test(NAME test-jit-shared-export-import-const-functions COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_functions.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_functions.ts") +tslang_add_test(NAME test-jit-shared-export-import-c-varargs COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_c_varargs.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_c_varargs.ts") tslang_add_test(NAME test-jit-shared-export-import-generators COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_generators.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_generators.ts") tslang_add_test(NAME test-jit-shared-export-import-class-extends COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends.ts") tslang_add_test(NAME test-jit-shared-export-import-throw-class COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_throw_class.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_throw_class.ts") @@ -1922,6 +1927,7 @@ set(TSLANG_CORPUS 00for_of.ts 00for.ts 00funcs_bindings.ts + 00funcs_c_varargs_storage.ts 00funcs_capture.ts 00funcs_expression_generic.ts 00funcs_expression_iterator.ts @@ -2710,7 +2716,9 @@ set_tests_properties(test-compile-diagnostic-location-rest-parameter-type FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed|rest_parameter_type\\.ts:1:|rest_parameter_type\\.ts:4:|verification") # A call with fewer arguments than the callee's required parameters is an error (TS2554), -# not padded with undef. Each file in call-arity/ makes one bad call; the test passes when +# not padded with undef; so is one with more arguments than the callee has parameters +# (too_many_*), which went silently into a slot the callee does not have (a rest parameter +# takes any number). Each file in call-arity/ makes one bad call; the test passes when # the compiler prints the expected message. The counts are what the user wrote, so `this` # (a method, a constructor) and a closure's capture are not counted. Plain add_test: these # compile nothing, so they need no x86 twin. @@ -2724,7 +2732,15 @@ set(call_arity_cases "declared|Expected 2 arguments, but got 1\\." "varargs|Expected at least 1 arguments, but got 0\\." "optional_call|Expected 2 arguments, but got 1\\." - "optional_call_value|Expected 2 arguments, but got 1\\.") + "optional_call_value|Expected 2 arguments, but got 1\\." + "too_many_plain|Expected 2 arguments, but got 3\\." + "too_many_optional|Expected 2\\-3 arguments, but got 4\\." + "too_many_method|Expected 2 arguments, but got 3\\." + "too_many_constructor|Expected 2 arguments, but got 3\\." + "too_many_closure|Expected 2 arguments, but got 3\\." + "too_many_optional_call|Expected 2 arguments, but got 3\\." + "too_many_value|Expected 1 arguments, but got 2\\." + "too_many_param|Expected 1 arguments, but got 2\\.") foreach(call_arity_case ${call_arity_cases}) string(FIND "${call_arity_case}" "|" call_arity_sep) string(SUBSTRING "${call_arity_case}" 0 ${call_arity_sep} call_arity_name) @@ -3731,6 +3747,29 @@ endforeach() # parameter, and varargs with and without extra arguments. tslang_add_test(NAME test-compile-call-arity-positive COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/call-arity/positive.ts") +# A C variadic function (`@varargs`) called through a value of it - a `let`, a `typeof printf` +# parameter - is a variadic call, as a call by name is. Lowered as a fixed-arity call, a double +# printed 0.000000 on Win64; since the arity check it was "Expected 1 arguments, but got 2.". +# The arguments to `...` get C's default promotions (an f32 printed 0.000000 even by name). +add_test(NAME test-jit-c-varargs-value-call + COMMAND $ --emit=jit --no-default-lib + --shared-libs=$ + "${PROJECT_SOURCE_DIR}/test/tester/c-varargs/value_call.ts") +set_tests_properties(test-jit-c-varargs-value-call + PROPERTIES PASS_REGULAR_EXPRESSION "direct 1\\.500000.*let 1\\.500000.*no extra arguments.*param 1\\.500000.*mixed 7 2\\.500000 9 0\\.250000.*param 1\\.500000.*mixed 7 2\\.500000 9 0\\.250000.*promoted 1\\.500000 -3 1.*promoted 1\\.500000 -3 1" + FAIL_REGULAR_EXPRESSION "Stack dump|error:|0\\.000000") + +# A C variadic function type and a plain one do not stand in for each other, either way. +foreach(c_varargs_name to_plain from_plain) + add_test(NAME test-compile-c-varargs-${c_varargs_name} + COMMAND $ --emit=obj --no-default-lib -mm=none + "${PROJECT_SOURCE_DIR}/test/tester/c-varargs/${c_varargs_name}.ts" + -o "${CMAKE_CURRENT_BINARY_DIR}/c-varargs-${c_varargs_name}.obj") + set_tests_properties(test-compile-c-varargs-${c_varargs_name} + PROPERTIES PASS_REGULAR_EXPRESSION "only one of them is a C variadic function" + FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed") +endforeach() + # A declared function with a second signature: one external symbol has one signature, and the # error says so (it was "redefinition of symbol"). add_test(NAME test-compile-declare-overloads-error diff --git a/tslang/test/tester/c-varargs/from_plain.ts b/tslang/test/tester/c-varargs/from_plain.ts new file mode 100644 index 000000000..54ffddb70 --- /dev/null +++ b/tslang/test/tester/c-varargs/from_plain.ts @@ -0,0 +1,12 @@ +// Nor is a plain function a C variadic one: a variadic call would hand it its arguments the C +// `...` way. +@varargs +declare function printf(format: string): s32; + +function viaParam(f: typeof printf) { + f("%d\n", 1); +} + +function main() { + viaParam((format: string) => 0); +} diff --git a/tslang/test/tester/c-varargs/to_plain.ts b/tslang/test/tester/c-varargs/to_plain.ts new file mode 100644 index 000000000..2e30132ce --- /dev/null +++ b/tslang/test/tester/c-varargs/to_plain.ts @@ -0,0 +1,9 @@ +// A C variadic function is not a plain function value: a call through the plain type would +// pass the extra arguments the fixed way. +@varargs +declare function printf(format: string): s32; + +function main() { + let q: (format: string) => s32 = printf; + q("x\n"); +} diff --git a/tslang/test/tester/c-varargs/value_call.ts b/tslang/test/tester/c-varargs/value_call.ts new file mode 100644 index 000000000..141de11e7 --- /dev/null +++ b/tslang/test/tester/c-varargs/value_call.ts @@ -0,0 +1,29 @@ +// A C variadic function called through a value of it - a `let`, a `typeof printf` parameter - +// is a variadic call, as a call by name is: a double after the format reaches printf (on Win64 +// it printed 0.000000 when the call was lowered as a fixed-arity one). +@varargs +declare function printf(format: string): s32; + +function viaParam(f: typeof printf) { + f("param %f\n", 1.5); + f("mixed %d %f %d %f\n", 7, 2.5, 9, 0.25); +} + +function main() { + printf("direct %f\n", 1.5); + + let p = printf; + p("let %f\n", 1.5); + p("no extra arguments\n"); + + viaParam(printf); + viaParam(p); + + // C's default argument promotions: an f32 goes to `...` as a double, an s8 and a boolean + // as an int + let x: f32 = 1.5; + let small: s8 = -3; + let flag = true; + printf("promoted %f %d %d\n", x, small, flag); + p("promoted %f %d %d\n", x, small, flag); +} diff --git a/tslang/test/tester/call-arity/positive.ts b/tslang/test/tester/call-arity/positive.ts index 9174120bf..6e56c43a5 100644 --- a/tslang/test/tester/call-arity/positive.ts +++ b/tslang/test/tester/call-arity/positive.ts @@ -17,7 +17,14 @@ function withUndefined(a: number, u: undefined) { return a; } +function fixed(a: number, b: number) { + return a + b; +} + function main() { + // a tuple spread fills exactly the parameters it has elements for + const pair: [number, number] = [1, 2]; + assert(fixed(...pair) == 3); assert(opt(1, 2) == 3); assert(def(1) == 6); assert(va(1) == 1); diff --git a/tslang/test/tester/call-arity/too_many_closure.ts b/tslang/test/tester/call-arity/too_many_closure.ts new file mode 100644 index 000000000..734987eb9 --- /dev/null +++ b/tslang/test/tester/call-arity/too_many_closure.ts @@ -0,0 +1,5 @@ +function main() { + let captured = 10; + const add = (a: number, b: number) => a + b + captured; + print(add(1, 2, 3)); +} diff --git a/tslang/test/tester/call-arity/too_many_constructor.ts b/tslang/test/tester/call-arity/too_many_constructor.ts new file mode 100644 index 000000000..16e5109be --- /dev/null +++ b/tslang/test/tester/call-arity/too_many_constructor.ts @@ -0,0 +1,8 @@ +class P { + constructor(public x: number, public y: number) {} +} + +function main() { + const p = new P(1, 2, 3); + print(p.x); +} diff --git a/tslang/test/tester/call-arity/too_many_method.ts b/tslang/test/tester/call-arity/too_many_method.ts new file mode 100644 index 000000000..03ddbe4c3 --- /dev/null +++ b/tslang/test/tester/call-arity/too_many_method.ts @@ -0,0 +1,10 @@ +class O { + m(a: number, b: number) { + return a + b; + } +} + +function main() { + const o = new O(); + print(o.m(1, 2, 3)); +} diff --git a/tslang/test/tester/call-arity/too_many_optional.ts b/tslang/test/tester/call-arity/too_many_optional.ts new file mode 100644 index 000000000..96af4ef23 --- /dev/null +++ b/tslang/test/tester/call-arity/too_many_optional.ts @@ -0,0 +1,7 @@ +function opt(a: number, b: number, c?: number) { + return a + b; +} + +function main() { + print(opt(1, 2, 3, 4)); +} diff --git a/tslang/test/tester/call-arity/too_many_optional_call.ts b/tslang/test/tester/call-arity/too_many_optional_call.ts new file mode 100644 index 000000000..6bd074aea --- /dev/null +++ b/tslang/test/tester/call-arity/too_many_optional_call.ts @@ -0,0 +1,10 @@ +class O { + m(a: number, b: number) { + return a + b; + } +} + +function main() { + const o: O | undefined = new O(); + print(o?.m(1, 2, 3)); +} diff --git a/tslang/test/tester/call-arity/too_many_param.ts b/tslang/test/tester/call-arity/too_many_param.ts new file mode 100644 index 000000000..8316ce077 --- /dev/null +++ b/tslang/test/tester/call-arity/too_many_param.ts @@ -0,0 +1,7 @@ +function call(f: (a: number) => void) { + f(1, 2); +} + +function main() { + call((a: number) => { print(a); }); +} diff --git a/tslang/test/tester/call-arity/too_many_plain.ts b/tslang/test/tester/call-arity/too_many_plain.ts new file mode 100644 index 000000000..7890d7213 --- /dev/null +++ b/tslang/test/tester/call-arity/too_many_plain.ts @@ -0,0 +1,7 @@ +function add(a: number, b: number) { + return a + b; +} + +function main() { + print(add(1, 2, 3)); +} diff --git a/tslang/test/tester/call-arity/too_many_value.ts b/tslang/test/tester/call-arity/too_many_value.ts new file mode 100644 index 000000000..eca9f153b --- /dev/null +++ b/tslang/test/tester/call-arity/too_many_value.ts @@ -0,0 +1,7 @@ +// A function value without @varargs takes no extra arguments. +declare function puts(s: string): s32; + +function main() { + let p = puts; + p("a", "b"); +} diff --git a/tslang/test/tester/tests/00funcs_c_varargs_storage.ts b/tslang/test/tester/tests/00funcs_c_varargs_storage.ts new file mode 100644 index 000000000..4108923e4 --- /dev/null +++ b/tslang/test/tester/tests/00funcs_c_varargs_storage.ts @@ -0,0 +1,34 @@ +// A C variadic function held where a function value is stored - a class field, an object +// member, an optional, a `CVarArgs<...>` annotation - is still called the C variadic way. printf +// returns how many characters it wrote: "1.5\n" is 4, a lost double prints "0\n", 2. +@varargs +declare function printf(format: string): s32; + +class Holder { + f: typeof printf = printf; + + constructor(public g: typeof printf) {} +} + +function pick(useIt: boolean): CVarArgs<(format: string) => s32> | undefined { + return useIt ? printf : undefined; +} + +function main() { + const h = new Holder(printf); + assert(h.f("%g\n", 1.5) == 4); + assert(h.g("%g %d\n", 2.5, 7) == 6); + + const o = { f: printf }; + assert(o.f("%g\n", 1.5) == 4); + + const u = pick(true); + if (u) { + assert(u("%g\n", 1.5) == 4); + } + + const c: CVarArgs<(format: string) => s32> = printf; + assert(c("%g\n", 1.5) == 4); + + print("done."); +} diff --git a/tslang/test/tester/tests/export_c_varargs.ts b/tslang/test/tester/tests/export_c_varargs.ts new file mode 100644 index 000000000..8c0339ca1 --- /dev/null +++ b/tslang/test/tester/tests/export_c_varargs.ts @@ -0,0 +1,15 @@ +// The library side of import_c_varargs.ts: a C variadic function exported as a variable, and +// taken as a parameter and a field. Its declaration in __decls names the type +// `CVarArgs<(p0: string) => s32>`, so importers call it the C variadic way too. +@varargs +declare function printf(format: string): s32; + +export let cprintf = printf; + +export function callWith(f: CVarArgs<(format: string) => s32>) { + return f("%g\n", 2.5); +} + +export class Printer { + constructor(public f: CVarArgs<(format: string) => s32>) {} +} diff --git a/tslang/test/tester/tests/import_c_varargs.ts b/tslang/test/tester/tests/import_c_varargs.ts new file mode 100644 index 000000000..cedcba69c --- /dev/null +++ b/tslang/test/tester/tests/import_c_varargs.ts @@ -0,0 +1,14 @@ +import './export_c_varargs' + +// See export_c_varargs.ts. printf returns how many characters it wrote: "1.5\n" is 4, a lost +// double prints "0\n", 2. +function main() { + assert(cprintf("%g\n", 1.5) == 4); + assert(cprintf("%d %g\n", 7, 12345.5) == 10); + assert(callWith(cprintf) == 4); + + const p = new Printer(cprintf); + assert(p.f("%g\n", 1.5) == 4); + + print("done."); +}