From cc81ac5f45b01cb486bc03e73c5844c34f456fdc Mon Sep 17 00:00:00 2001 From: Mark Waddingham Date: Mon, 3 Jul 2017 16:51:32 +0100 Subject: [PATCH 1/4] [[ LCB ]] Add varargs support to foreign handlers This patch adds varargs support to foreign C handlers, allowing binding to a wider range of C functions. The syntax for vararg handlers is similar to that of C: ``` foreign handler myvarargs(in pFirst as Pointer, ...) returns CInt binds to ... ``` Using the token `...` to indicate the start of the variable parameter list. To implement vararg functions, a new parameter mode 'variadic' has been added which causes a handler to be treated as variadic if such a parameter with a mode is present. As C requires any int type with rank less than int to be promoted to int, and float to be promoted to double (when passed to a non-fixed parameter in a variadic function), a 'promotedtype' and 'promote' function have been added to foreign type descriptors to describe the appropriate relation. --- .../LiveCode Builder Language Reference.md | 8 + docs/lcb/notes/19991.md | 11 + libfoundation/include/foundation.h | 15 +- libfoundation/src/foundation-foreign.cpp | 130 +++++- libfoundation/src/foundation-private.h | 1 + libfoundation/src/foundation-typeinfo.cpp | 21 + libscript/include/libscript/script.h | 1 + libscript/src/module-foreign.cpp | 6 + libscript/src/script-execute.cpp | 396 +++++++++++++----- libscript/src/script-execute.hpp | 18 + libscript/src/script-private.h | 2 + ..._compilertestrunnerbehavior.livecodescript | 13 +- .../compiler/frontend/variadic.compilertest | 59 +++ tests/lcb/vm/foreign-invoke.lcb | 37 ++ toolchain/lc-compile/src/bind.g | 7 +- toolchain/lc-compile/src/check.g | 51 ++- toolchain/lc-compile/src/emit.cpp | 8 + toolchain/lc-compile/src/generate.g | 36 +- toolchain/lc-compile/src/grammar.g | 5 + toolchain/lc-compile/src/support.g | 7 + toolchain/lc-compile/src/types.g | 12 + toolchain/libcompile/src/literal.c | 14 + toolchain/libcompile/src/report.c | 3 + 23 files changed, 735 insertions(+), 126 deletions(-) create mode 100644 docs/lcb/notes/19991.md create mode 100644 tests/lcb/compiler/frontend/variadic.compilertest diff --git a/docs/guides/LiveCode Builder Language Reference.md b/docs/guides/LiveCode Builder Language Reference.md index d4f441210ec..3b5ee3afa8c 100644 --- a/docs/guides/LiveCode Builder Language Reference.md +++ b/docs/guides/LiveCode Builder Language Reference.md @@ -436,6 +436,14 @@ statement blocks. A foreign handler definition binds an identifier to a handler defined in foreign code. +The last parameter in a foreign handler declaration may be '...' to indicate +that the handler is variadic. This allows binding to C functions such as +sprintf. + +Note: No bridging of types will occur when passing a parameter in the non-fixed +section of a variadic argument list. You must ensure the arguments you pass there +are of the appropriate foreign type (e.g. CInt, CDouble). + There are a number of types defined in the foreign module which map to the appropriate foreign type when used in foreign handler signatures. diff --git a/docs/lcb/notes/19991.md b/docs/lcb/notes/19991.md new file mode 100644 index 00000000000..6c36121fa83 --- /dev/null +++ b/docs/lcb/notes/19991.md @@ -0,0 +1,11 @@ +# LiveCode Builder Language + +## Variadic foreign C functions + +* It is now possible to bind to variadic C functions: + foreign handler printf(in pFormat as Pointer, ...) returns CInt binds to "" + In this case, the '...' must be the last parameter, and there must be at + least one fixed parameter. + +# [19991] Add support for variadic foreign C functions. + diff --git a/libfoundation/include/foundation.h b/libfoundation/include/foundation.h index 4b0d18b990b..1a8927e8fb1 100644 --- a/libfoundation/include/foundation.h +++ b/libfoundation/include/foundation.h @@ -1776,6 +1776,14 @@ struct MCForeignTypeDescriptor bool (*doimport)(void *contents, bool release, MCValueRef& r_value); bool (*doexport)(MCValueRef value, bool release, void *contents); bool (*describe)(void *contents, MCStringRef & r_desc); + + /* The promotedtype typeinfo is the type to which this type must be promoted + * when passed through variadic parameters. The 'promote' method does the + * promotion. */ + MCTypeInfoRef promotedtype; + /* Promote the value in contents as necessary. The slot ptr must be big enough + * to hold the promotedtype. */ + void (*promote)(void *contents); }; MC_DLLEXPORT bool MCForeignTypeInfoCreate(const MCForeignTypeDescriptor *descriptor, MCTypeInfoRef& r_typeinfo); @@ -1866,6 +1874,7 @@ enum MCHandlerTypeFieldMode kMCHandlerTypeFieldModeIn, kMCHandlerTypeFieldModeOut, kMCHandlerTypeFieldModeInOut, + kMCHandlerTypeFieldModeVariadic, }; struct MCHandlerTypeFieldInfo @@ -1890,12 +1899,16 @@ MC_DLLEXPORT bool MCForeignHandlerTypeInfoCreate(const MCHandlerTypeFieldInfo *f // Returns true if the handler is of foreign type. MC_DLLEXPORT bool MCHandlerTypeInfoIsForeign(MCTypeInfoRef typeinfo); + +// Returns true if the handler is variadic. +MC_DLLEXPORT bool MCHandlerTypeInfoIsVariadic(MCTypeInfoRef typeinfo); // Get the return type of the handler. A return-type of kMCNullTypeInfo means no // value is returned. MC_DLLEXPORT MCTypeInfoRef MCHandlerTypeInfoGetReturnType(MCTypeInfoRef typeinfo); -// Get the number of parameters the handler takes. +// Get the number of parameters the handler takes. If the handler is variadic, +// this returns the number of fixed parameters. MC_DLLEXPORT uindex_t MCHandlerTypeInfoGetParameterCount(MCTypeInfoRef typeinfo); // Return the mode of the index'th parameter. diff --git a/libfoundation/src/foundation-foreign.cpp b/libfoundation/src/foundation-foreign.cpp index 74cc744b2d6..09fefd13f5a 100644 --- a/libfoundation/src/foundation-foreign.cpp +++ b/libfoundation/src/foundation-foreign.cpp @@ -131,6 +131,9 @@ MC_DLLEXPORT_DEF MCTypeInfoRef MCForeignSIntTypeInfo() { return kMCSIntTypeInfo; //////////////////////////////////////////////////////////////////////////////// +static_assert(sizeof(int) == 4, + "Assumption that int is 4 bytes in size not valid"); + template struct compute_primitive_type { @@ -210,39 +213,54 @@ struct bool_type_desc_t { using bridge_type = MCBooleanRef; static constexpr MCTypeInfoRef& bridge_type_info() { return kMCBooleanTypeInfo; } static constexpr auto& hash_func = MCHashBool; + + static constexpr auto is_promotable = true; + static constexpr MCTypeInfoRef& promoted_type_info() { return kMCUInt32TypeInfo; } }; struct uint8_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCUInt8TypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = true; + static constexpr MCTypeInfoRef& promoted_type_info() { return kMCUInt32TypeInfo; } }; struct sint8_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCSInt8TypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = true; + static constexpr MCTypeInfoRef& promoted_type_info() { return kMCSInt32TypeInfo; } }; struct uint16_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCUInt16TypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = true; + static constexpr MCTypeInfoRef& promoted_type_info() { return kMCUInt32TypeInfo; } }; struct sint16_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCSInt16TypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = true; + static constexpr MCTypeInfoRef& promoted_type_info() { return kMCSInt32TypeInfo; } }; struct uint32_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCUInt32TypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct sint32_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCSInt32TypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct uint64_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCUInt64TypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct sint64_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCSInt64TypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct float_type_desc_t: public numeric_type_desc_t { @@ -251,6 +269,8 @@ struct float_type_desc_t: public numeric_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCFloatTypeInfo; } static constexpr auto describe_format = ""; static constexpr auto& hash_func = MCHashDouble; + static constexpr auto is_promotable = true; + static constexpr MCTypeInfoRef& promoted_type_info() { return kMCDoubleTypeInfo; } }; struct double_type_desc_t: public numeric_type_desc_t { using c_type = double; @@ -258,6 +278,7 @@ struct double_type_desc_t: public numeric_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCDoubleTypeInfo; } static constexpr auto describe_format = ""; static constexpr auto& hash_func = MCHashDouble; + static constexpr auto is_promotable = false; }; struct pointer_type_desc_t { @@ -269,24 +290,29 @@ struct pointer_type_desc_t { static constexpr auto describe_format = ""; static constexpr MCTypeInfoRef& base_type_info() { return kMCNullTypeInfo; } static constexpr auto& hash_func = MCHashPointer; + static constexpr auto is_promotable = false; }; struct uintsize_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCUIntSizeTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct sintsize_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCSIntSizeTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct uintptr_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCUIntPtrTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct sintptr_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCSIntPtrTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; /**/ @@ -298,46 +324,62 @@ struct cbool_type_desc_t: public bool_type_desc_t struct cchar_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCCCharTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = true; + static constexpr MCTypeInfoRef& promoted_type_info() { return kMCSInt32TypeInfo; } }; struct cuchar_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCCUCharTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = true; + static constexpr MCTypeInfoRef& promoted_type_info() { return kMCUInt32TypeInfo; } }; struct cschar_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCCSCharTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = true; + static constexpr MCTypeInfoRef& promoted_type_info() { return kMCSInt32TypeInfo; } }; struct cushort_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCCUShortTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = true; + static constexpr MCTypeInfoRef& promoted_type_info() { return kMCUInt32TypeInfo; } }; struct csshort_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCCSShortTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = true; + static constexpr MCTypeInfoRef& promoted_type_info() { return kMCSInt32TypeInfo; } }; struct cuint_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCCUIntTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct csint_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCCSIntTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct culong_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCCULongTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct cslong_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCCSLongTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct culonglong_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCCULongLongTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct cslonglong_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCCSLongLongTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct uint_type_desc_t: public integral_type_desc_t { @@ -346,6 +388,7 @@ struct uint_type_desc_t: public integral_type_desc_t { static constexpr auto primitive_type = kMCForeignPrimitiveTypeUInt32; static constexpr MCTypeInfoRef& type_info() { return kMCUIntTypeInfo; } static constexpr auto describe_format = ""; + static constexpr auto is_promotable = false; }; struct sint_type_desc_t: public integral_type_desc_t { static_assert(INTEGER_MAX == INT32_MAX, @@ -354,6 +397,7 @@ struct sint_type_desc_t: public integral_type_desc_t { static constexpr MCTypeInfoRef& type_info() { return kMCSIntTypeInfo; } static constexpr auto describe_format = ""; static constexpr auto& hash_func = MCHashInteger; + static constexpr auto is_promotable = false; }; @@ -558,6 +602,16 @@ struct DoImport } }; +template +struct DoPromote +{ + static_assert(sizeof(typename TypeDesc::c_type) == 0, "Missing promote specialization"); + static bool promote(void *contents) + { + return false; + } +}; + template bool initialize(void *contents) { @@ -638,6 +692,13 @@ bool doimport(void *contents, bool p_release, MCValueRef& r_value) reinterpret_cast(r_value)); } +template +void promote(void *contents) +{ + static_assert(TypeDesc::is_promotable, "This type is not promotable"); + return DoPromote::promote(contents); +} + /* ---------- bool specializations */ @@ -728,6 +789,16 @@ struct DoImport< } }; +template +struct DoPromote< + RealType, typename std::enable_if::value>::type> +{ + static void promote(void *contents) + { + *(double *)contents = *(typename RealType::c_type *)contents; + } +}; + /* ---------- integer numeric specializations */ template @@ -801,6 +872,28 @@ struct DoImport< } }; +template +struct DoPromote< + IntType, typename std::enable_if::value && + std::is_signed::value>::type> +{ + static void promote(void *contents) + { + *(int *)contents = *(typename IntType::c_type *)contents; + } +}; + +template +struct DoPromote< + UIntType, typename std::enable_if::value && + std::is_unsigned::value>::type> +{ + static void promote(void *contents) + { + *(unsigned int *)contents = *(typename UIntType::c_type *)contents; + } +}; + template class DescriptorBuilder { public: @@ -830,11 +923,14 @@ class DescriptorBuilder { hash, nullptr, /* doimport */ nullptr, /* doexport */ - describe + describe, + nullptr, /* promotedtype */ + nullptr, /* promote */ }; setup_optional(d); setup_bridge(d); + setup_promote(d); MCAutoStringRef t_name_string; if (!MCStringCreateWithCString(p_name, &t_name_string)) @@ -879,7 +975,7 @@ class DescriptorBuilder { } /* Setup the doimport() and doexport() methods depending on - * whether the ValueType is optional or not. */ + * whether the ValueType is bridgeable or not. */ template ::type = 0> @@ -899,6 +995,26 @@ class DescriptorBuilder { d.doimport = nullptr; d.doexport = nullptr; } + + /* Setup the promote() methods depending on whether the ValueType is + * promoteable or not. */ + template ::type = 0> + static void setup_promote(MCForeignTypeDescriptor& d) + { + d.promotedtype = PromotableType::promoted_type_info(); + d.promote = promote; + } + + template ::type = 0> + static void setup_promote(MCForeignTypeDescriptor& d) + { + d.promotedtype = kMCNullTypeInfo; + d.promote = nullptr; + } }; } /* anonymous namespace */ @@ -907,15 +1023,17 @@ class DescriptorBuilder { bool __MCForeignValueInitialize(void) { - if (!(DescriptorBuilder::create("__builtin__.bool") && - DescriptorBuilder::create("__builtin__.float") && + /* We must initialized uint32, sint32 and double first as they are used as + * promotions. */ + if (!(DescriptorBuilder::create("__builtin__.uint32") && + DescriptorBuilder::create("__builtin__.sint32") && DescriptorBuilder::create("__builtin__.double") && + DescriptorBuilder::create("__builtin__.bool") && + DescriptorBuilder::create("__builtin__.float") && DescriptorBuilder::create("__builtin__.uint8") && DescriptorBuilder::create("__builtin__.sint8") && DescriptorBuilder::create("__builtin__.uint16") && DescriptorBuilder::create("__builtin__.sint16") && - DescriptorBuilder::create("__builtin__.uint32") && - DescriptorBuilder::create("__builtin__.sint32") && DescriptorBuilder::create("__builtin__.uint64") && DescriptorBuilder::create("__builtin__.sint64") && DescriptorBuilder::create("__builtin__.uintsize") && diff --git a/libfoundation/src/foundation-private.h b/libfoundation/src/foundation-private.h index 5dae4ecdad6..77e3e9adecc 100755 --- a/libfoundation/src/foundation-private.h +++ b/libfoundation/src/foundation-private.h @@ -55,6 +55,7 @@ enum { kMCTypeInfoTypeCodeMask = 0xff, kMCTypeInfoFlagHandlerIsForeign = 1 << 8, + kMCTypeInfoFlagHandlerIsVariadic = 1 << 9, // We use typecodes well above the fixed ones we have to // indicate 'special' typeinfo (i.e. those with no real diff --git a/libfoundation/src/foundation-typeinfo.cpp b/libfoundation/src/foundation-typeinfo.cpp index ef783e202e1..89572cb3807 100644 --- a/libfoundation/src/foundation-typeinfo.cpp +++ b/libfoundation/src/foundation-typeinfo.cpp @@ -647,6 +647,8 @@ bool MCForeignTypeInfoCreate(const MCForeignTypeDescriptor *p_descriptor, MCType self -> foreign . descriptor . doimport = p_descriptor -> doimport; self -> foreign . descriptor . doexport = p_descriptor -> doexport; self -> foreign . descriptor . describe = p_descriptor -> describe; + self -> foreign . descriptor . promotedtype = MCValueRetain(p_descriptor->promotedtype); + self -> foreign . descriptor . promote = p_descriptor -> promote; if (!__MCForeignTypeInfoComputeLayoutType(self)) { @@ -803,6 +805,14 @@ static bool MCCommonHandlerTypeInfoCreate(bool p_is_foreign, const MCHandlerType for (index_t i = 0; i < p_field_count; ++i) { __MCAssertIsTypeInfo(p_fields[i].type); + + if (p_fields[i].mode == kMCHandlerTypeFieldModeVariadic) + { + p_field_count = i; + self->flags |= kMCTypeInfoFlagHandlerIsVariadic; + break; + } + self -> handler . fields[i] . type = MCValueRetain(p_fields[i] . type); self -> handler . fields[i] . mode = p_fields[i] . mode; } @@ -844,6 +854,17 @@ bool MCHandlerTypeInfoIsForeign(MCTypeInfoRef unresolved_self) return (self -> flags & kMCTypeInfoFlagHandlerIsForeign) != 0; } +MC_DLLEXPORT_DEF +bool MCHandlerTypeInfoIsVariadic(MCTypeInfoRef unresolved_self) +{ + MCTypeInfoRef self; + self = __MCTypeInfoResolve(unresolved_self); + + MCAssert(MCTypeInfoIsHandler(self)); + + return (self -> flags & kMCTypeInfoFlagHandlerIsVariadic) != 0; +} + MC_DLLEXPORT_DEF MCTypeInfoRef MCHandlerTypeInfoGetReturnType(MCTypeInfoRef unresolved_self) { diff --git a/libscript/include/libscript/script.h b/libscript/include/libscript/script.h index c0e93546a56..4eba21ec5cc 100644 --- a/libscript/include/libscript/script.h +++ b/libscript/include/libscript/script.h @@ -347,6 +347,7 @@ enum MCScriptHandlerTypeParameterMode kMCScriptHandlerTypeParameterModeIn, kMCScriptHandlerTypeParameterModeOut, kMCScriptHandlerTypeParameterModeInOut, + kMCScriptHandlerTypeParameterModeVariadic, kMCScriptHandlerTypeParameterMode__Last }; diff --git a/libscript/src/module-foreign.cpp b/libscript/src/module-foreign.cpp index 4a8b8892441..af044683ce6 100644 --- a/libscript/src/module-foreign.cpp +++ b/libscript/src/module-foreign.cpp @@ -334,6 +334,8 @@ extern "C" bool com_livecode_foreign_Initialize(void) d . doimport = __nativecstring_import; d . doexport = __nativecstring_export; d . describe = nullptr; + d . promotedtype = kMCNullTypeInfo; + d . promote = nullptr; if (!__build_typeinfo("com.livecode.foreign.NativeCString", &d, kMCNativeCStringTypeInfo)) return false; @@ -353,6 +355,8 @@ extern "C" bool com_livecode_foreign_Initialize(void) d . doimport = __wstring_import; d . doexport = __wstring_export; d . describe = nullptr; + d . promotedtype = kMCNullTypeInfo; + d . promote = nullptr; if (!__build_typeinfo("com.livecode.foreign.WString", &d, kMCWStringTypeInfo)) return false; @@ -372,6 +376,8 @@ extern "C" bool com_livecode_foreign_Initialize(void) d . doimport = __utf8string_import; d . doexport = __utf8string_export; d . describe = nullptr; + d . promotedtype = kMCNullTypeInfo; + d . promote = nullptr; if (!__build_typeinfo("com.livecode.foreign.UTF8String", &d, kMCUTF8StringTypeInfo)) return false; diff --git a/libscript/src/script-execute.cpp b/libscript/src/script-execute.cpp index 248069efa54..8f9cb4207fc 100644 --- a/libscript/src/script-execute.cpp +++ b/libscript/src/script-execute.cpp @@ -117,7 +117,8 @@ class MCScriptForeignInvocation // Append a non-reference argument - this takes contents_ptr bool Argument(void *p_slot_ptr, - __MCScriptValueDrop p_slot_drop) + __MCScriptValueDrop p_slot_drop, + ffi_type* p_type) { if (m_argument_count >= kMaxArguments) { @@ -130,6 +131,9 @@ class MCScriptForeignInvocation // Store the slot ptr and its drop. m_argument_slots[m_argument_count] = p_slot_ptr; m_argument_drops[m_argument_count] = p_slot_drop; + + // Store the ffi_type. + m_argument_types[m_argument_count] = p_type; // Bump the number of arguments m_argument_count++; @@ -155,6 +159,9 @@ class MCScriptForeignInvocation // Store the slot ptr and its drop. m_argument_slots[m_argument_count] = p_slot_ptr; m_argument_drops[m_argument_count] = p_slot_drop; + + // Reference arguments always have type ffi_pointer. + m_argument_types[m_argument_count] = &ffi_type_pointer; // Bump the number of arguments m_argument_count++; @@ -202,10 +209,32 @@ class MCScriptForeignInvocation break; case kMCScriptForeignHandlerLanguageC: - ffi_call((ffi_cif *)p_handler ->c.function_cif, - (void(*)())p_handler ->c.function, - p_result_slot_ptr, - m_argument_values); + if (!MCHandlerTypeInfoIsVariadic(p_handler_signature)) + { + ffi_call((ffi_cif *)p_handler ->c.function_cif, + (void(*)())p_handler ->c.function, + p_result_slot_ptr, + m_argument_values); + } + else + { + ffi_cif *t_fixed_cif = (ffi_cif *)p_handler->c.function_cif; + ffi_cif t_cif; + if (ffi_prep_cif_var(&t_cif, + t_fixed_cif->abi, + t_fixed_cif->nargs, + m_argument_count, + t_fixed_cif->rtype, + m_argument_types) != FFI_OK) + { + return MCErrorThrowGeneric(MCSTR("unexpected libffi failure")); + } + + ffi_call(&t_cif, + (void(*)())p_handler ->c.function, + p_result_slot_ptr, + m_argument_values); + } break; case kMCScriptForeignHandlerLanguageBuiltinC: @@ -250,22 +279,24 @@ class MCScriptForeignInvocation __MCScriptValueDrop m_argument_drops[kMaxArguments]; // The actual values in the slots (not indirected for references). void *m_argument_slots[kMaxArguments]; + // The ffi_types of the arguments (used for var-args) + ffi_type *m_argument_types[kMaxArguments]; size_t m_storage_frontier; char m_stack_storage[kMaxStorage]; }; inline void -__MCScriptComputeSlotAttributes(const MCResolvedTypeInfo& p_slot_type, +__MCScriptComputeSlotAttributes(MCTypeInfoRef p_slot_type, size_t& r_slot_size, size_t& r_slot_align, __MCScriptValueDrop& r_slot_drop) { // If the slot is foreign then we interrogate the foreign descriptor. - if (MCTypeInfoIsForeign(p_slot_type.type)) + if (MCTypeInfoIsForeign(p_slot_type)) { const MCForeignTypeDescriptor *t_desc = - MCForeignTypeInfoGetDescriptor(p_slot_type.type); + MCForeignTypeInfoGetDescriptor(p_slot_type); r_slot_size = t_desc->size; r_slot_align = t_desc->size; @@ -275,8 +306,8 @@ __MCScriptComputeSlotAttributes(const MCResolvedTypeInfo& p_slot_type, } // If the slot is a foreign handler then it is pointer sized. - if (MCTypeInfoIsHandler(p_slot_type.type) && - MCHandlerTypeInfoIsForeign(p_slot_type.type)) + if (MCTypeInfoIsHandler(p_slot_type) && + MCHandlerTypeInfoIsForeign(p_slot_type)) { r_slot_size = sizeof(void*); r_slot_align = alignof(void*); @@ -289,6 +320,219 @@ __MCScriptComputeSlotAttributes(const MCResolvedTypeInfo& p_slot_type, r_slot_drop = __MCScriptDropValueRef; } +bool +MCScriptExecuteContext::InvokeForeignVarArgument(MCScriptForeignInvocation& p_invocation, + MCScriptInstanceRef p_instance, + MCScriptForeignHandlerDefinition *p_handler_def, + uindex_t p_arg_index, + uindex_t p_arg_reg) +{ + // Get the value in the register, this determines the type. + MCValueRef t_arg_value = nil; + t_arg_value = CheckedFetchRegister(p_arg_reg); + if (t_arg_value == nil) + { + return false; + } + + // Resolve the type of the value. + MCResolvedTypeInfo t_resolved_type; + if (!ResolveTypeInfo(MCValueGetTypeInfo(t_arg_value), + t_resolved_type)) + { + return false; + } + + // Store the actual type of the value. + MCTypeInfoRef t_actual_type = t_resolved_type.type; + + // Fetch the promoted type of arg type (sub ints promote to int, and float + // promotes to double). + const MCForeignTypeDescriptor *t_desc = nullptr; + if (MCTypeInfoIsForeign(t_actual_type)) + { + t_desc = MCForeignTypeInfoGetDescriptor(t_actual_type); + } + + MCTypeInfoRef t_arg_type = t_actual_type; + if (t_desc->promote != nullptr) + { + MCResolvedTypeInfo t_resolved_arg_type; + if (!ResolveTypeInfo(t_desc->promotedtype, + t_resolved_arg_type)) + { + return false; + } + + t_arg_type = t_resolved_arg_type.type; + } + + // Compute the slot attributes - using the (potentially) promoted type. + size_t t_slot_size = 0; + size_t t_slot_align = 0; + __MCScriptValueDrop t_slot_drop = nil; + __MCScriptComputeSlotAttributes(t_arg_type, + t_slot_size, + t_slot_align, + t_slot_drop); + + // Allocate room for the slot in the invocation. + void *t_slot_ptr = nil; + if (!p_invocation.Allocate(t_slot_size, + t_slot_align, + t_slot_ptr)) + { + Rethrow(); + return false; + } + + // Convert the value to an unboxed one - we use the actual type of the argument + // here. + // TODO: Split UnboxingConvert so that we don't resolve types twice. + if (!UnboxingConvert(t_arg_value, + t_resolved_type, + t_slot_ptr)) + { + return false; + } + + // If we don't get a slot out, then it failed. + if (t_slot_ptr == nil) + { + ThrowInvalidValueForArgument(p_instance, + p_handler_def, + p_arg_index, + t_arg_value); + return false; + } + + // If we must promote the type, do it next. Note that the slot ptr is big + // enough to hold the promoted value, and we do this 'in place. + if (t_arg_type != t_actual_type) + { + t_desc->promote(t_slot_ptr); + } + + // Fetch the ffi_type to use - notice that we must use the *promoted* type + // here. + ffi_type *t_type = nullptr; + if (t_desc != nullptr) + { + t_type = (ffi_type *)MCForeignTypeInfoGetLayoutType(t_arg_type); + } + else + { + t_type = &ffi_type_pointer; + } + + if (!p_invocation.Argument(t_slot_ptr, + t_slot_drop, + t_type)) + { + Rethrow(); + return false; + } + + return true; +} + +bool +MCScriptExecuteContext::InvokeForeignArgument(MCScriptForeignInvocation& p_invocation, + MCScriptInstanceRef p_instance, + MCScriptForeignHandlerDefinition *p_handler_def, + uindex_t p_arg_index, + MCHandlerTypeFieldMode p_mode, + MCTypeInfoRef p_param_type, + uindex_t p_arg_reg) +{ + // Compute the parameter's slot size and allocate storage for it + MCResolvedTypeInfo t_resolved_param_type; + if (!ResolveTypeInfo(p_param_type, + t_resolved_param_type)) + { + return false; + } + + size_t t_slot_size = 0; + size_t t_slot_align = 0; + __MCScriptValueDrop t_slot_drop = nil; + __MCScriptComputeSlotAttributes(t_resolved_param_type.type, + t_slot_size, + t_slot_align, + t_slot_drop); + + void *t_slot_ptr = nil; + if (!p_invocation.Allocate(t_slot_size, + t_slot_align, + t_slot_ptr)) + { + Rethrow(); + return false; + } + + // If the mode is not out, then we have an initial value to initialize + // it with; otherwise we must initialize it directly. + MCValueRef t_arg_value = nil; + if (p_mode != kMCHandlerTypeFieldModeOut) + { + t_arg_value = CheckedFetchRegister(p_arg_reg); + + if (t_arg_value == nil) + { + return false; + } + } + + // Convert the value to an unboxed one. + if (!UnboxingConvert(t_arg_value, + t_resolved_param_type, + t_slot_ptr)) + { + return false; + } + + if (t_slot_ptr == nil) + { + ThrowInvalidValueForArgument(p_instance, + p_handler_def, + p_arg_index, + t_arg_value); + return false; + } + + if (p_mode == kMCHandlerTypeFieldModeIn) + { + ffi_type *t_type = nullptr; + if (MCTypeInfoIsForeign(t_resolved_param_type.type)) + { + t_type = (ffi_type *)MCForeignTypeInfoGetLayoutType(t_resolved_param_type.type); + } + else + { + t_type = &ffi_type_pointer; + } + + if (!p_invocation.Argument(t_slot_ptr, + t_slot_drop, + t_type)) + { + Rethrow(); + return false; + } + } + else + { + if (!p_invocation.ReferenceArgument(t_slot_ptr, + t_slot_drop)) + { + Rethrow(); + return false; + } + } + + return true; +} + void MCScriptExecuteContext::InvokeForeign(MCScriptInstanceRef p_instance, MCScriptForeignHandlerDefinition *p_handler_def, @@ -318,21 +562,31 @@ MCScriptExecuteContext::InvokeForeign(MCScriptInstanceRef p_instance, MCTypeInfoRef t_signature = GetSignatureOfHandler(p_instance, p_handler_def); + + // Determine whether the handler is variadic + bool t_is_variadic = + MCHandlerTypeInfoIsVariadic(t_signature); + // Fetch the minimum parameter count (the fixed arg count for variadiac + // handlers). + uindex_t t_fixed_arg_count = + MCHandlerTypeInfoGetParameterCount(t_signature); + // Check the parameter count. - if (MCHandlerTypeInfoGetParameterCount(t_signature) != p_argument_regs.size()) - { - ThrowWrongNumberOfArguments(p_instance, - p_handler_def, - p_argument_regs.size()); - return; - } + if ((!t_is_variadic && t_fixed_arg_count != p_argument_regs.size()) || + (t_is_variadic && t_fixed_arg_count > p_argument_regs.size())) + { + ThrowWrongNumberOfArguments(p_instance, + p_handler_def, + p_argument_regs.size()); + return; + } + // Process the fixed arguments MCScriptForeignInvocation t_invocation; - for(uindex_t i = 0; i < MCHandlerTypeInfoGetParameterCount(t_signature); ++i) + for(uindex_t i = 0; i < t_fixed_arg_count; ++i) { // Fetch the parameter mode and type - MCHandlerTypeFieldMode t_param_mode = MCHandlerTypeInfoGetParameterMode(t_signature, i); @@ -341,85 +595,37 @@ MCScriptExecuteContext::InvokeForeign(MCScriptInstanceRef p_instance, MCHandlerTypeInfoGetParameterType(t_signature, i); - // Compute the parameter's slot size and allocate storage for it - - MCResolvedTypeInfo t_resolved_param_type; - if (!ResolveTypeInfo(t_param_type, - t_resolved_param_type)) - { - return; - } - - size_t t_slot_size = 0; - size_t t_slot_align = 0; - __MCScriptValueDrop t_slot_drop = nil; - __MCScriptComputeSlotAttributes(t_resolved_param_type, - t_slot_size, - t_slot_align, - t_slot_drop); - - void *t_slot_ptr = nil; - if (!t_invocation.Allocate(t_slot_size, - t_slot_align, - t_slot_ptr)) - { - Rethrow(); - return; - } - - // If the mode is not out, then we have an initial value to initialize - // it with; otherwise we must initialize it directly. - MCValueRef t_arg_value = nil; - if (t_param_mode != kMCHandlerTypeFieldModeOut) - { - t_arg_value = CheckedFetchRegister(p_argument_regs[i]); - - if (t_arg_value == nil) - { - return; - } - } - - // Convert the value to an unboxed one. - if (!UnboxingConvert(t_arg_value, - t_resolved_param_type, - t_slot_ptr)) - { - return; - } - - if (t_slot_ptr == nil) - { - ThrowInvalidValueForArgument(p_instance, - p_handler_def, - i, - t_arg_value); - return; - } - - if (t_param_mode == kMCHandlerTypeFieldModeIn) - { - if (!t_invocation.Argument(t_slot_ptr, - t_slot_drop)) - { - Rethrow(); - return; - } - } - else - { - if (!t_invocation.ReferenceArgument(t_slot_ptr, - t_slot_drop)) - { - Rethrow(); - return; - } - } + // Process the argument + if (!InvokeForeignArgument(t_invocation, + p_instance, + p_handler_def, + i, + t_param_mode, + t_param_type, + p_argument_regs[i])) + { + return; + } } + // If variadic, process the non-fixed arguments. + if (t_is_variadic) + { + for(uindex_t i = t_fixed_arg_count; i < p_argument_regs.size(); i++) + { + if (!InvokeForeignVarArgument(t_invocation, + p_instance, + p_handler_def, + i, + p_argument_regs[i])) + { + return; + } + } + } + // Fetch the return value type. MCResolvedTypeInfo t_return_value_type; - if (!ResolveTypeInfo(MCHandlerTypeInfoGetReturnType(t_signature), t_return_value_type)) { @@ -434,7 +640,7 @@ MCScriptExecuteContext::InvokeForeign(MCScriptInstanceRef p_instance, void *t_return_value_slot_ptr = nil; if (t_return_value_type.named_type != kMCNullTypeInfo) { - __MCScriptComputeSlotAttributes(t_return_value_type, + __MCScriptComputeSlotAttributes(t_return_value_type.type, t_return_value_slot_size, t_return_value_slot_align, t_return_value_slot_drop); diff --git a/libscript/src/script-execute.hpp b/libscript/src/script-execute.hpp index 7fc36f2f137..f1073d62d6e 100644 --- a/libscript/src/script-execute.hpp +++ b/libscript/src/script-execute.hpp @@ -18,6 +18,8 @@ #include "script-private.h" +class MCScriptForeignInvocation; + class MCScriptExecuteContext { public: @@ -140,6 +142,22 @@ class MCScriptExecuteContext // no return value. void PopFrame(uindex_t result_reg); + // Accumulate a non-fixed var-arg arg into the invocation. + bool InvokeForeignVarArgument(MCScriptForeignInvocation& invocation, + MCScriptInstanceRef instance, + MCScriptForeignHandlerDefinition *handler_def, + uindex_t arg_index, + uindex_t arg_reg); + + // Accumulate a fixed arg into the invocation. + bool InvokeForeignArgument(MCScriptForeignInvocation& p_invocation, + MCScriptInstanceRef p_instance, + MCScriptForeignHandlerDefinition *p_handler_def, + uindex_t arg_index, + MCHandlerTypeFieldMode mode, + MCTypeInfoRef type, + uindex_t arg_reg); + // Invoke a foreign function with the given arguments, taken from registers // returning the value into the result register. void InvokeForeign(MCScriptInstanceRef instance, diff --git a/libscript/src/script-private.h b/libscript/src/script-private.h index 5e70e512478..48170de9291 100644 --- a/libscript/src/script-private.h +++ b/libscript/src/script-private.h @@ -184,6 +184,8 @@ struct MCScriptHandlerTypeParameter return kMCHandlerTypeFieldModeOut; case kMCScriptHandlerTypeParameterModeInOut: return kMCHandlerTypeFieldModeInOut; + case kMCScriptHandlerTypeParameterModeVariadic: + return kMCHandlerTypeFieldModeVariadic; default: MCUnreachableReturn(kMCHandlerTypeFieldModeIn); } diff --git a/tests/_compilertestrunnerbehavior.livecodescript b/tests/_compilertestrunnerbehavior.livecodescript index 407dc076278..6984b0b73bf 100644 --- a/tests/_compilertestrunnerbehavior.livecodescript +++ b/tests/_compilertestrunnerbehavior.livecodescript @@ -108,10 +108,15 @@ private command runCompilerTest pInfo, pScriptFile, pTest local tCommandLine -- First we need to process the specified test in script file - + local tTestInfo - processCompilerTest pInfo, pScriptFile, pTest, tTestInfo - + try + processCompilerTest pInfo, pScriptFile, pTest, tTestInfo + catch tError + write tError & return to stderr + quit 1 + end try + local tCompilerOutput, tCompilerExitStatus dispatch "CompilerTestRunner_DoRunTest" to me with tTestInfo, tCompilerOutput, tCompilerExitStatus reportCompilerTestDiag tCompilerOutput @@ -426,7 +431,7 @@ private command processCompilerTest pInfo, pScriptFile, pTest, @rCompilerTest end if end if end repeat - + if tName is empty then reportCompilerTestError pScriptFile, tLineNumber, format("test '%s' not found in file", pTest) end if diff --git a/tests/lcb/compiler/frontend/variadic.compilertest b/tests/lcb/compiler/frontend/variadic.compilertest new file mode 100644 index 00000000000..b6007049246 --- /dev/null +++ b/tests/lcb/compiler/frontend/variadic.compilertest @@ -0,0 +1,59 @@ +%% Copyright (C) 2017 LiveCode Ltd. +%% +%% This file is part of LiveCode. +%% +%% LiveCode is free software; you can redistribute it and/or modify it under +%% the terms of the GNU General Public License v3 as published by the Free +%% Software Foundation. +%% +%% LiveCode is distributed in the hope that it will be useful, but WITHOUT ANY +%% WARRANTY; without even the implied warranty of MERCHANTABILITY or +%% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +%% for more details. +%% +%% You should have received a copy of the GNU General Public License +%% along with LiveCode. If not see . + +%% Variadic parameter must be the last in the parameter list +%TEST VariadicLastInForeignHandler +module compiler_test +foreign handler TestVariadic(in pA as any, ...) returns nothing binds to "" +end module +%EXPECT PASS +%SUCCESS +%ENDTEST + +%% Variadic parameter must be the last in the parameter list +%TEST VariadicNotLastInForeignHandler +module compiler_test +foreign handler TestVariadic(in pA as any, %{BEFORE_VARIADIC}..., in pB as any) returns nothing binds to "" +end module +%EXPECT PASS +%ERROR "Variadic parameter must be the last" AT BEFORE_VARIADIC +%ENDTEST + +%% Variadic parameters are not allowed in non-foreign handlers +%TEST VariadicNotAllowedInNonForeign +module compiler_test +handler TestVariadic(in pA as any, %{BEFORE_VARIADIC}...) returns nothing +end handler +end module +%EXPECT PASS +%ERROR "Variadic parameters only allowed in foreign handlers" AT BEFORE_VARIADIC +%ENDTEST + +%% You can have 0 to any number of variadic arguments +%TEST VariadicParametersAnyCount +module compiler_test +__safe foreign handler TestVariadic(in pA as any, ...) returns nothing binds to "" +handler CallVariadic() + TestVariadic(1) + TestVariadic(1, 2) + TestVariadic(1, 2, 3) +end handler +end module +%EXPECT PASS +%SUCCESS +%ENDTEST + +%%%ERROR "Too few arguments for specified handler" AT BEFORE_ZERO \ No newline at end of file diff --git a/tests/lcb/vm/foreign-invoke.lcb b/tests/lcb/vm/foreign-invoke.lcb index 113102e4af7..dc8e1ce62d2 100644 --- a/tests/lcb/vm/foreign-invoke.lcb +++ b/tests/lcb/vm/foreign-invoke.lcb @@ -18,4 +18,41 @@ public handler TestForeignInvoke_OptionalPointerResult() test "non-nullptr maps to non-nothing for optional Pointer" when tNonNullNativeCharPtr is not nothing end handler +-- + +foreign handler malloc(in pSize as UIntSize) returns Pointer binds to "" +foreign handler free(in pBlock as Pointer) returns nothing binds to "" +foreign handler sprintf(in pTarget as Pointer, in pFormat as ZStringNative, ...) returns CInt binds to "" +foreign handler MCStringCreateWithCString(in pCString as Pointer, out rString as String) returns CBool binds to "" + +public handler TestForeignInvoke_Varargs() + variable tString1 as String + variable tString2 as String + unsafe + variable tOutputBuffer as Pointer + put malloc(4096) into tOutputBuffer + + sprintf(tOutputBuffer, "no formats") + MCStringCreateWithCString(tOutputBuffer, tString1) + + variable tInt as SInt16 + variable tLong as SInt32 + variable tLongLong as SInt64 + variable tFloat as CFloat + variable tDouble as CDouble + put 1000 into tInt + put 1000000000 into tLong + put tLong * 1000000 into tLongLong + put 3.5 into tFloat + put 7.5 into tDouble + sprintf(tOutputBuffer, "%d %ld %lld %.1f %.1lf", tInt, tLong, tLongLong, tFloat, tDouble) + MCStringCreateWithCString(tOutputBuffer, tString2) + + free(tOutputBuffer) + end unsafe + test "sprintf works with no variadic arguments" when tString1 is "no formats" + test diagnostic tString2 + test "sprintf works with variadic arguments" when tString2 is "1000 1000000000 1000000000000000 3.5 7.5" +end handler + end module diff --git a/toolchain/lc-compile/src/bind.g b/toolchain/lc-compile/src/bind.g index 1c8cfb9a819..605b25946d1 100644 --- a/toolchain/lc-compile/src/bind.g +++ b/toolchain/lc-compile/src/bind.g @@ -238,7 +238,7 @@ 'rule' DeclareParameters(parameterlist(parameter(_, _, Name, _), Tail)): DeclareId(Name) DeclareParameters(Tail) - + 'rule' DeclareParameters(nil): -- do nothing @@ -334,7 +334,7 @@ 'rule' DefineParameters(ModuleId, parameterlist(parameter(_, _, Name, Type), Tail)): DefineSymbolId(Name, ModuleId, inferred, parameter, Type) DefineParameters(ModuleId, Tail) - + 'rule' DefineParameters(ModuleId, nil): -- do nothing @@ -904,7 +904,8 @@ 'rule' DumpBindings(PARAMETER'parameter(_, _, Name, Type)): DumpId("parameter", Name) DumpBindings(Type) - + + 'rule' DumpBindings(STATEMENT'variable(_, Name, Type)): DumpId("local variable", Name) DumpBindings(Type) diff --git a/toolchain/lc-compile/src/check.g b/toolchain/lc-compile/src/check.g index 160dcdc22e7..7b144cd8a1f 100644 --- a/toolchain/lc-compile/src/check.g +++ b/toolchain/lc-compile/src/check.g @@ -54,6 +54,9 @@ -- Check that safe / unsafe boundaries are respected CheckSafety(Module) + -- Check that variadic parameters only appear in foreign handlers + CheckVariadic(Module) + -------------------------------------------------------------------------------- -- At this point all identifiers either have a defined meaning, or are defined @@ -1458,6 +1461,14 @@ 'rule' CheckCallArguments(_, nil, nil): -- done! + -- variadic parameter 'sticks' and matches the rest of the argument list + 'rule' CheckCallArguments(Position, ParamRest:parameterlist(parameter(_, variadic, _, _), _), expressionlist(Argument, ArgRest)): + CheckExpressionIsEvaluatable(Argument) + CheckCallArguments(Position, ParamRest, ArgRest) + + 'rule' CheckCallArguments(Position, parameterlist(parameter(_, variadic, _, _), _), nil): + -- done! + 'rule' CheckCallArguments(Position, parameterlist(parameter(_, Mode, _, _), ParamRest), expressionlist(Argument, ArgRest)): [| ne(Mode, out) @@ -1647,13 +1658,17 @@ 'action' CheckForeignHandlerParameterTypes(PARAMETERLIST) + 'rule' CheckForeignHandlerParameterTypes(parameterlist(parameter(_, variadic, _, _), Rest)): + CheckForeignHandlerParameterTypes(Rest) + 'rule' CheckForeignHandlerParameterTypes(parameterlist(parameter(Position, _, _, Type), Rest)): [| where(Type -> unspecified) Error_NoTypeSpecifiedForForeignHandlerParameter(Position) |] CheckForeignHandlerParameterTypes(Rest) - + + 'rule' CheckForeignHandlerParameterTypes(nil): -- do nothing @@ -2081,6 +2096,30 @@ -------------------------------------------------------------------------------- +'sweep' CheckVariadic(ANY) + + 'rule' CheckVariadic(DEFINITION'handler(_, _, Name, signature(Parameters, _), _, _)): + [| + ParameterListContainsVariadic(Parameters -> Position) + Error_VariadicParametersOnlyAllowedInForeignHandlers(Position) + |] + + 'rule' CheckVariadic(PARAMETERLIST'parameterlist(parameter(Position, variadic, _, _), Tail)): + [| + ne(Tail, nil) + Error_VariadicParameterMustBeLast(Position) + |] + +'condition' ParameterListContainsVariadic(PARAMETERLIST -> POS) + + 'rule' ParameterListContainsVariadic(parameterlist(parameter(Position, variadic, _, _), _) -> Position): + -- + + 'rule' ParameterListContainsVariadic(parameterlist(_, Tail) -> Position): + ParameterListContainsVariadic(Tail -> Position) + +-------------------------------------------------------------------------------- + 'condition' QueryHandlerIdSignature(ID -> SIGNATURE) 'rule' QueryHandlerIdSignature(Id -> Signature) @@ -2148,13 +2187,3 @@ 'action' GetQualifiedName(ID -> NAME) -------------------------------------------------------------------------------- - -'action' QueryExpressionListLength(EXPRESSIONLIST -> INT) - - 'rule' QueryExpressionListLength(expressionlist(_, Tail) -> TailCount + 1) - QueryExpressionListLength(Tail -> TailCount) - - 'rule' QueryExpressionListLength(nil -> 0) - -- nothing - --------------------------------------------------------------------------------- diff --git a/toolchain/lc-compile/src/emit.cpp b/toolchain/lc-compile/src/emit.cpp index 52a5658309f..9206656aef4 100644 --- a/toolchain/lc-compile/src/emit.cpp +++ b/toolchain/lc-compile/src/emit.cpp @@ -112,6 +112,7 @@ extern "C" void EmitBeginForeignHandlerType(intptr_t return_type_index); extern "C" void EmitHandlerTypeInParameter(NameRef name, intptr_t type_index); extern "C" void EmitHandlerTypeOutParameter(NameRef name, intptr_t type_index); extern "C" void EmitHandlerTypeInOutParameter(NameRef name, intptr_t type_index); +extern "C" void EmitHandlerTypeVariadicParameter(NameRef name); extern "C" void EmitEndHandlerType(intptr_t& r_index); extern "C" void EmitHandlerParameter(NameRef name, intptr_t type_index, intptr_t& r_index); extern "C" void EmitHandlerVariable(NameRef name, intptr_t type_index, intptr_t& r_index); @@ -1539,6 +1540,13 @@ void EmitHandlerTypeInOutParameter(NameRef name, intptr_t type_index) EmitHandlerTypeParameter(kMCHandlerTypeFieldModeInOut, name, type_index); } +void EmitHandlerTypeVariadicParameter(NameRef name) +{ + intptr_t type_index; + EmitListType(type_index); + EmitHandlerTypeParameter(kMCHandlerTypeFieldModeVariadic, name, type_index); +} + void EmitEndHandlerType(intptr_t& r_type_index) { uindex_t t_index; diff --git a/toolchain/lc-compile/src/generate.g b/toolchain/lc-compile/src/generate.g index 384bd31ea42..72df96ebeda 100644 --- a/toolchain/lc-compile/src/generate.g +++ b/toolchain/lc-compile/src/generate.g @@ -1351,6 +1351,13 @@ 'rule' GenerateInvoke_EvaluateArguments(_, _, nil, _): -- do nothing. +'action' GenerateInvoke_EvaluateVariadicArgument(INT, INT, EXPRESSIONLIST, INT) + + 'rule' GenerateInvoke_EvaluateVariadicArgument(ResultReg, ContextReg, Args, Index): + GetExpressionAtIndex(Args, Index -> Expr) + GenerateInvoke_EvaluateArgumentForIn(ResultReg, ContextReg, Expr) + GenerateInvoke_EvaluateVariadicArgument(ResultReg, ContextReg, Args, Index + 1) + -- In arguments are simple, just evaluate the expr into a register attached to the -- node. 'action' GenerateInvoke_EvaluateArgumentForIn(INT, INT, EXPRESSION) @@ -1667,7 +1674,11 @@ Info'Kind -> Kind Info'Type -> Type FullyResolveType(Type -> handler(_, _, signature(HandlerSig, _))) - GenerateCall_GetInvokeSignature(HandlerSig, 0 -> InvokeSig) + + -- Pass the actual argument count to invoke sig computation to take into + -- account variadic parameters. + QueryExpressionListLength(Arguments -> ArgCount) + GenerateCall_GetInvokeSignature(HandlerSig, 0, ArgCount -> InvokeSig) (| -- If the Id is not a handler it must be a variable @@ -1697,12 +1708,22 @@ EmitDestroyRegister(HandlerReg) |] -'action' GenerateCall_GetInvokeSignature(PARAMETERLIST, INT -> INVOKESIGNATURE) +'action' GenerateCall_GetInvokeSignature(PARAMETERLIST, INT, INT -> INVOKESIGNATURE) + + -- When we encounter a variadic parameter, we generate in parameters for the + -- rest of the arg list. + 'rule' GenerateCall_GetInvokeSignature(ParamRest:parameterlist(parameter(_, variadic, _, _), _), Index, ArgCount -> invokesignature(in, Index, InvokeRest)): + (| + lt(Index, ArgCount) + GenerateCall_GetInvokeSignature(ParamRest, Index + 1, ArgCount -> InvokeRest) + || + where(INVOKESIGNATURE'nil -> InvokeRest) + |) - 'rule' GenerateCall_GetInvokeSignature(parameterlist(parameter(_, Mode, _, _), ParamRest), Index -> invokesignature(Mode, Index, InvokeRest)): - GenerateCall_GetInvokeSignature(ParamRest, Index + 1 -> InvokeRest) + 'rule' GenerateCall_GetInvokeSignature(parameterlist(parameter(_, Mode, _, _), ParamRest), Index, ArgCount -> invokesignature(Mode, Index, InvokeRest)): + GenerateCall_GetInvokeSignature(ParamRest, Index + 1, ArgCount -> InvokeRest) - 'rule' GenerateCall_GetInvokeSignature(nil, _ -> nil): + 'rule' GenerateCall_GetInvokeSignature(nil, _, ArgCount -> nil): -- do nothing ---- @@ -2176,9 +2197,12 @@ || where(Mode -> inout) EmitHandlerTypeInOutParameter(Name, TypeIndex) + || + where(Mode -> variadic) + EmitHandlerTypeVariadicParameter(Name) |) GenerateHandlerTypeParameters(Rest) - + 'rule' GenerateHandlerTypeParameters(nil): -- nothing diff --git a/toolchain/lc-compile/src/grammar.g b/toolchain/lc-compile/src/grammar.g index be41559a436..426c7fa5383 100644 --- a/toolchain/lc-compile/src/grammar.g +++ b/toolchain/lc-compile/src/grammar.g @@ -513,6 +513,11 @@ 'rule' Parameter(-> parameter(Position, in, Name, Type)): Identifier(-> Name) @(-> Position) OptionalTypeClause(-> Type) + + 'rule' Parameter(-> parameter(Position, variadic, Name, unspecified)): + "..." @(-> Position) + MakeNameLiteral("" -> Identifier) + AssignId(Position, Identifier, nil -> Name) 'nonterm' Mode(-> MODE) diff --git a/toolchain/lc-compile/src/support.g b/toolchain/lc-compile/src/support.g index 5ed717056ca..e7b03b0d7ae 100644 --- a/toolchain/lc-compile/src/support.g +++ b/toolchain/lc-compile/src/support.g @@ -209,6 +209,7 @@ EmitHandlerTypeInParameter EmitHandlerTypeOutParameter EmitHandlerTypeInOutParameter + EmitHandlerTypeVariadicParameter EmitEndHandlerType EmitHandlerParameter EmitHandlerVariable @@ -353,6 +354,8 @@ Error_BytecodeNotAllowedInSafeContext Error_UnsafeHandlerCallNotAllowedInSafeContext Error_InvalidNameForNamespace + Error_VariadicParametersOnlyAllowedInForeignHandlers + Error_VariadicParameterMustBeLast Warning_MetadataClausesShouldComeAfterUseClauses Warning_DeprecatedTypeName @@ -612,6 +615,7 @@ 'action' EmitHandlerTypeInParameter(Name: NAME, Type: INT) 'action' EmitHandlerTypeOutParameter(Name: NAME, Type: INT) 'action' EmitHandlerTypeInOutParameter(Name: NAME, Type: INT) +'action' EmitHandlerTypeVariadicParameter(Name: NAME) 'action' EmitEndHandlerType(-> INT) 'action' EmitHandlerParameter(Name: NAME, Type: INT -> Index: INT) @@ -780,6 +784,9 @@ 'action' Error_BytecodeNotAllowedInSafeContext(Position: POS) 'action' Error_UnsafeHandlerCallNotAllowedInSafeContext(Position: POS, Identifier: NAME) +'action' Error_VariadicParametersOnlyAllowedInForeignHandlers(Position: POS) +'action' Error_VariadicParameterMustBeLast(Position: POS) + 'action' Warning_MetadataClausesShouldComeAfterUseClauses(Position: POS) 'action' Warning_DeprecatedTypeName(Position: POS, NewType: STRING) 'action' Warning_UnsuitableNameForDefinition(Position: POS, Identifier: NAME) diff --git a/toolchain/lc-compile/src/types.g b/toolchain/lc-compile/src/types.g index be42941a2b7..0d1e8decb4d 100644 --- a/toolchain/lc-compile/src/types.g +++ b/toolchain/lc-compile/src/types.g @@ -39,6 +39,7 @@ NAME DOUBLE SYNTAXPRECEDENCE BYTECODE + QueryExpressionListLength -------------------------------------------------------------------------------- @@ -126,6 +127,7 @@ in out inout + variadic 'type' BYTECODE sequence(Left: BYTECODE, Right: BYTECODE) @@ -377,3 +379,13 @@ 'type' DOUBLE -------------------------------------------------------------------------------- + +'action' QueryExpressionListLength(EXPRESSIONLIST -> INT) + + 'rule' QueryExpressionListLength(expressionlist(_, Tail) -> TailCount + 1) + QueryExpressionListLength(Tail -> TailCount) + + 'rule' QueryExpressionListLength(nil -> 0) + -- nothing + +-------------------------------------------------------------------------------- diff --git a/toolchain/libcompile/src/literal.c b/toolchain/libcompile/src/literal.c index e413d43258c..e34fb3799d5 100644 --- a/toolchain/libcompile/src/literal.c +++ b/toolchain/libcompile/src/literal.c @@ -912,6 +912,13 @@ IsNameValidForNamespace(NameRef p_id) GetStringOfNameLiteral (p_id, &t_id); + /* Empty names are always fine (as they are only allowed in specific + * contexts) */ + if (*t_id == '\0') + { + return 1; + } + /* Must not start with a digit */ if (t_id[0] >= '0' && t_id[0] <= '9') @@ -932,6 +939,13 @@ IsNameSuitableForDefinition (NameRef p_id) GetStringOfNameLiteral (p_id, &t_id); + /* Empty names are always fine (as they are only allowed in specific + * contexts) */ + if (*t_id == '\0') + { + return 1; + } + for(i = 0; '\0' != t_id[i]; ++i) { /* We ignore any number of _ before the identifier */ diff --git a/toolchain/libcompile/src/report.c b/toolchain/libcompile/src/report.c index 4372c31fa34..adc42bcf337 100644 --- a/toolchain/libcompile/src/report.c +++ b/toolchain/libcompile/src/report.c @@ -401,6 +401,9 @@ DEFINE_ERROR(IllegalNumberOfArgumentsForOpcode, "Wrong number of arguments for o DEFINE_ERROR(BytecodeNotAllowedInSafeContext, "Bytecode blocks can only be present in unsafe context") DEFINE_ERROR_I(UnsafeHandlerCallNotAllowedInSafeContext, "Unsafe handler '%s' can only be called in unsafe context") +DEFINE_ERROR(VariadicParameterMustBeLast, "Variadic parameter must be the last") +DEFINE_ERROR(VariadicParametersOnlyAllowedInForeignHandlers, "Variadic parameters only allowed in foreign handlers") + #define DEFINE_WARNING(Name, Message) \ void Warning_##Name(intptr_t p_position) { _Warning(p_position, Message); } #define DEFINE_WARNING_I(Name, Message) \ From 8d26c38fca641ca61aa4d54ffdd66478ea9034f0 Mon Sep 17 00:00:00 2001 From: Mark Waddingham Date: Fri, 7 Jul 2017 09:20:08 +0100 Subject: [PATCH 2/4] [[ LCB ]] Ensure an error is thrown if binding varargs to Java This patch makes it so that JavaCheckSignature will fail if an attempt is made to bind to a variadic signature. --- libfoundation/src/foundation-java-private.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libfoundation/src/foundation-java-private.cpp b/libfoundation/src/foundation-java-private.cpp index b1c30598642..d53e07fbd63 100644 --- a/libfoundation/src/foundation-java-private.cpp +++ b/libfoundation/src/foundation-java-private.cpp @@ -151,6 +151,9 @@ static bool __RemoveSurroundingParentheses(MCStringRef p_in, MCStringRef& r_out) bool MCJavaPrivateCheckSignature(MCTypeInfoRef p_signature, MCStringRef p_args, MCStringRef p_return, int p_call_type) { + if (MCHandlerTypeInfoIsVariadic(p_signature)) + return false; + MCJavaCallType t_call_type = static_cast(p_call_type); if (t_call_type == MCJavaCallTypeInterfaceProxy) return true; @@ -216,7 +219,7 @@ bool MCJavaPrivateErrorsInitialize() if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.java.NativeMethodCallError"), MCNAME("java"), MCSTR("JNI exception thrown when calling native method"), kMCJavaNativeMethodCallErrorTypeInfo)) return false; - if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.java.BindingStringSignatureError"), MCNAME("java"), MCSTR("Java binding string does not match foreign handler signature"), kMCJavaBindingStringSignatureErrorTypeInfo)) + if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.java.BindingStringSignatureError"), MCNAME("java"), MCSTR("Java binding string does not match foreign handler signature or signature not supported"), kMCJavaBindingStringSignatureErrorTypeInfo)) return false; if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.java.CouldNotInitialiseJREError"), MCNAME("java"), MCSTR("Could not initialise Java Runtime Environment"), kMCJavaCouldNotInitialiseJREErrorTypeInfo)) From 867e88115e6ac7506c962d102e7116c90aff3a48 Mon Sep 17 00:00:00 2001 From: Mark Waddingham Date: Fri, 7 Jul 2017 09:21:07 +0100 Subject: [PATCH 3/4] [[ LCB ]] Ensure variadic parameter position is checked This patch ensures that the position of the variadic parameter is valid when creating a handler typeinfo. If it is not, the creation fails, and an error is thrown. --- libfoundation/src/foundation-typeinfo.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libfoundation/src/foundation-typeinfo.cpp b/libfoundation/src/foundation-typeinfo.cpp index 89572cb3807..c625fa58886 100644 --- a/libfoundation/src/foundation-typeinfo.cpp +++ b/libfoundation/src/foundation-typeinfo.cpp @@ -808,6 +808,12 @@ static bool MCCommonHandlerTypeInfoCreate(bool p_is_foreign, const MCHandlerType if (p_fields[i].mode == kMCHandlerTypeFieldModeVariadic) { + if (i == 0 || p_field_count != i + 1) + { + MCValueRelease(self); + return MCErrorThrowGeneric(MCSTR("Variadic parameter cannot be first, and must be last")); + } + p_field_count = i; self->flags |= kMCTypeInfoFlagHandlerIsVariadic; break; From a8b2d34de3bdbb854b83940179ed7cebe34a7457 Mon Sep 17 00:00:00 2001 From: Mark Waddingham Date: Fri, 7 Jul 2017 09:21:54 +0100 Subject: [PATCH 4/4] [[ LCB ]] Tidy up source This patch tidies up a case of variable definition and initialization being in separate commands. --- libscript/src/script-execute.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libscript/src/script-execute.cpp b/libscript/src/script-execute.cpp index 8f9cb4207fc..f79be12dd7d 100644 --- a/libscript/src/script-execute.cpp +++ b/libscript/src/script-execute.cpp @@ -328,8 +328,8 @@ MCScriptExecuteContext::InvokeForeignVarArgument(MCScriptForeignInvocation& p_in uindex_t p_arg_reg) { // Get the value in the register, this determines the type. - MCValueRef t_arg_value = nil; - t_arg_value = CheckedFetchRegister(p_arg_reg); + MCValueRef t_arg_value = + CheckedFetchRegister(p_arg_reg); if (t_arg_value == nil) { return false;