diff --git a/src/builtins.c b/src/builtins.c index 75e3f87151672..37471bbb7a718 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1395,6 +1395,8 @@ JL_CALLABLE(jl_f__typebody) break; } } + if (!dt->name->mutabl && !dt->name->references_self) + dt->name->mayinlinealloc = 1; } } diff --git a/src/ccall.cpp b/src/ccall.cpp index c66e86867dc87..00a6403d6434b 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -493,7 +493,7 @@ static Value *julia_to_native( assert(!byRef); // don't expect any ABI to pass pointers by pointer return boxed(ctx, jvinfo); } - assert(jl_is_datatype(jlto) && julia_struct_has_layout((jl_datatype_t*)jlto, jlto_env)); + assert(jl_is_datatype(jlto) && julia_struct_has_layout((jl_datatype_t*)jlto)); typeassert_input(ctx, jvinfo, jlto, jlto_env, argn); if (!byRef) @@ -1065,7 +1065,7 @@ std::string generate_func_sig(const char *fname) } } - t = _julia_struct_to_llvm(ctx, tti, unionall_env, &isboxed, llvmcall); + t = _julia_struct_to_llvm(ctx, tti, &isboxed, llvmcall); if (t == NULL || t == T_void) { return make_errmsg(fname, i + 1, " doesn't correspond to a C type"); } @@ -1211,7 +1211,7 @@ static const std::string verify_ccall_sig(jl_value_t *&rt, jl_value_t *at, rt = (jl_value_t*)jl_any_type; } - lrt = _julia_struct_to_llvm(ctx, rt, unionall_env, &retboxed, llvmcall); + lrt = _julia_struct_to_llvm(ctx, rt, &retboxed, llvmcall); if (lrt == NULL) return "return type doesn't correspond to a C type"; @@ -1405,7 +1405,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) isboxed = false; } else { - largty = _julia_struct_to_llvm(&ctx.emission_context, tti, unionall, &isboxed, llvmcall); + largty = _julia_struct_to_llvm(&ctx.emission_context, tti, &isboxed, llvmcall); } if (isboxed) { ary = boxed(ctx, argv[0]); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index c3f14299a133e..7f88d2d2fff4b 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -475,7 +475,7 @@ static Value *emit_struct_gep(jl_codectx_t &ctx, Type *lty, Value *base, unsigne return ctx.builder.CreateConstInBoundsGEP2_32(lty, base, 0, idx); } -static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, jl_unionall_t *ua, bool *isboxed, bool llvmcall=false); +static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, bool *isboxed, bool llvmcall=false); static Type *_julia_type_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, bool *isboxed) { @@ -486,7 +486,7 @@ static Type *_julia_type_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, bool if (jl_is_concrete_immutable(jt)) { if (jl_datatype_nbits(jt) == 0) return T_void; - Type *t = _julia_struct_to_llvm(ctx, jt, NULL, isboxed); + Type *t = _julia_struct_to_llvm(ctx, jt, isboxed); assert(t != NULL); return t; } @@ -542,23 +542,10 @@ static bool jl_type_hasptr(jl_value_t* typ) return jl_is_datatype(typ) && ((jl_datatype_t*)typ)->layout->npointers > 0; } -// compute whether all concrete subtypes of this type have the same layout -// (which is conservatively approximated here by asking whether the types of any of the -// fields depend on any of the parameters of the containing type) -static bool julia_struct_has_layout(jl_datatype_t *dt, jl_unionall_t *ua) +// return whether all concrete subtypes of this type have the same layout +static bool julia_struct_has_layout(jl_datatype_t *dt) { - if (dt->layout) - return true; - if (ua) { - jl_svec_t *types = jl_get_fieldtypes(dt); - size_t i, ntypes = jl_svec_len(types); - for (i = 0; i < ntypes; i++) { - jl_value_t *ty = jl_svecref(types, i); - if (jl_has_typevar_from_unionall(ty, ua)) - return false; - } - } - return true; + return dt->layout || jl_has_fixed_layout(dt); } static unsigned jl_field_align(jl_datatype_t *dt, size_t i) @@ -569,7 +556,7 @@ static unsigned jl_field_align(jl_datatype_t *dt, size_t i) return std::min({al, (unsigned)jl_datatype_align(dt), (unsigned)JL_HEAP_ALIGNMENT}); } -static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, jl_unionall_t *ua_env, bool *isboxed, bool llvmcall) +static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, bool *isboxed, bool llvmcall) { // this function converts a Julia Type into the equivalent LLVM struct // use this where C-compatible (unboxed) structs are desired @@ -584,7 +571,12 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, jl_ bool isTuple = jl_is_tuple_type(jt); jl_svec_t *ftypes = jl_get_fieldtypes(jst); size_t i, ntypes = jl_svec_len(ftypes); - if (ntypes == 0 || (jst->layout && jl_datatype_nbits(jst) == 0)) + if (!julia_struct_has_layout(jst)) + return NULL; // caller should have checked jl_type_mappable_to_c already, but we'll be nice + if (jst->layout == NULL) + jl_compute_field_offsets(jst); + assert(jst->layout); + if (ntypes == 0 || jl_datatype_nbits(jst) == 0) return T_void; Type *_struct_decl = NULL; // TODO: we should probably make a temporary root for `jst` somewhere @@ -592,8 +584,6 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, jl_ Type *&struct_decl = (ctx && !llvmcall ? ctx->llvmtypes[jst] : _struct_decl); if (struct_decl) return struct_decl; - if (!julia_struct_has_layout(jst, ua_env)) - return NULL; std::vector latypes(0); bool isarray = true; bool isvector = true; @@ -605,17 +595,8 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, jl_ if (jlasttype != NULL && ty != jlasttype) isvector = false; jlasttype = ty; - size_t fsz = 0, al = 0; - bool isptr = !jl_islayout_inline(ty, &fsz, &al); - if (jst->layout) { - // NOTE: jl_field_isptr can disagree with jl_islayout_inline here if the - // struct decided this field must be a pointer due to a type circularity. - // Example from issue #40050: `struct B <: Ref{Tuple{B}}; end` - isptr = jl_field_isptr(jst, i); - assert((isptr ? sizeof(void*) : fsz + jl_is_uniontype(ty)) == jl_field_size(jst, i)); - } Type *lty; - if (isptr) { + if (jl_field_isptr(jst, i)) { lty = T_prjlvalue; isvector = false; } @@ -626,13 +607,15 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, jl_ // pick an Integer type size such that alignment will generally be correct, // and always end with an Int8 (selector byte). // We may need to insert padding first to get to the right offset + size_t fsz = 0, al = 0; + bool isptr = !jl_islayout_inline(ty, &fsz, &al); + assert(!isptr && fsz == jl_field_size(jst, i) - 1); (void)isptr; if (al > MAX_ALIGN) { Type *AlignmentType; AlignmentType = ArrayType::get(FixedVectorType::get(T_int8, al), 0); latypes.push_back(AlignmentType); al = MAX_ALIGN; } - assert(al <= jl_field_align(jst, i)); Type *AlignmentType = IntegerType::get(jl_LLVMContext, 8 * al); unsigned NumATy = fsz / al; unsigned remainder = fsz % al; @@ -647,8 +630,9 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, jl_ continue; } else { - lty = _julia_struct_to_llvm(ctx, ty, NULL, &isptr, llvmcall); - assert(!isptr); + bool isptr; + lty = _julia_struct_to_llvm(ctx, ty, &isptr, llvmcall); + assert(lty && !isptr); } if (lasttype != NULL && lasttype != lty) isarray = false; @@ -703,16 +687,9 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, jl_ return T_prjlvalue; } -static Type *julia_struct_to_llvm(jl_codectx_t &ctx, jl_value_t *jt, jl_unionall_t *ua, bool *isboxed) -{ - return _julia_struct_to_llvm(&ctx.emission_context, jt, ua, isboxed); -} - -bool jl_type_mappable_to_c(jl_value_t *ty) +static Type *julia_struct_to_llvm(jl_codectx_t &ctx, jl_value_t *jt, bool *isboxed) { - jl_codegen_params_t params; - bool toboxed; - return _julia_struct_to_llvm(¶ms, ty, NULL, &toboxed) != NULL; + return _julia_struct_to_llvm(&ctx.emission_context, jt, isboxed); } static bool is_datatype_all_pointers(jl_datatype_t *dt) diff --git a/src/codegen.cpp b/src/codegen.cpp index 1a2837737d203..4944356a73dbb 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -920,11 +920,11 @@ static bool jl_is_pointerfree(jl_value_t* t) // these queries are usually related, but we split them out here // for convenience and clarity (and because it changes the calling convention) -static bool deserves_stack(jl_value_t* t, bool pointerfree=false) +static bool deserves_stack(jl_value_t* t) { if (!jl_is_concrete_immutable(t)) return false; - return ((jl_datatype_t*)t)->isinlinealloc; + return jl_datatype_isinlinealloc((jl_datatype_t*)t, 0); } static bool deserves_argbox(jl_value_t* t) { @@ -3042,6 +3042,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, jl_value_t *jt = jl_tparam0(utt); if (jl_is_vararg(jt)) jt = jl_unwrap_vararg(jt); + assert(jl_is_datatype(jt)); Value *vidx = emit_unbox(ctx, T_size, fld, (jl_value_t*)jl_long_type); // This is not necessary for correctness, but allows to omit // the extra code for getting the length of the tuple @@ -3053,7 +3054,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, emit_datatype_nfields(ctx, emit_typeof_boxed(ctx, obj)), jl_true); } - bool isboxed = !jl_datatype_isinlinealloc(jt); + bool isboxed = !jl_datatype_isinlinealloc((jl_datatype_t*)jt, 0); Value *ptr = maybe_decay_tracked(ctx, data_pointer(ctx, obj)); *ret = typed_load(ctx, ptr, vidx, isboxed ? (jl_value_t*)jl_any_type : jt, @@ -5424,10 +5425,7 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con // some sanity checking and check whether there's a vararg size_t nargt = jl_svec_len(argt); bool isVa = (nargt > 0 && jl_is_vararg(jl_svecref(argt, nargt - 1))); - if (isVa) { - emit_error(ctx, "cfunction: Vararg syntax not allowed for argument list"); - return jl_cgval_t(); - } + assert(!isVa); jl_array_t *closure_types = NULL; jl_value_t *sigt = NULL; // dispatch-sig = type signature with Ref{} annotations removed and applied to the env @@ -5576,7 +5574,7 @@ const char *jl_generate_ccallable(void *llvmmod, void *sysimg_handle, jl_value_t crt = (jl_value_t*)jl_any_type; } bool toboxed; - Type *lcrt = _julia_struct_to_llvm(¶ms, crt, NULL, &toboxed); + Type *lcrt = _julia_struct_to_llvm(¶ms, crt, &toboxed); if (toboxed) lcrt = T_prjlvalue; size_t nargs = jl_nparams(sigt)-1; @@ -6325,7 +6323,7 @@ static std::pair, jl_llvm_functions_t> if (allunbox) return; } - else if (deserves_stack(jt, true)) { + else if (deserves_stack(jt)) { bool isboxed; Type *vtype = julia_type_to_llvm(ctx, jt, &isboxed); assert(!isboxed); diff --git a/src/datatype.c b/src/datatype.c index cfa4b368b825c..b65c8a602bfcf 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -76,6 +76,7 @@ JL_DLLEXPORT jl_typename_t *jl_new_typename_in(jl_sym_t *name, jl_module_t *modu tn->abstract = abstract; tn->mutabl = mutabl; tn->references_self = 0; + tn->mayinlinealloc = 0; tn->mt = NULL; tn->partial = NULL; return tn; @@ -97,7 +98,6 @@ jl_datatype_t *jl_new_uninitialized_datatype(void) t->isdispatchtuple = 0; t->isbitstype = 0; t->zeroinit = 0; - t->isinlinealloc = 0; t->has_concrete_subtype = 1; t->cached_by_hash = 0; t->name = NULL; @@ -238,6 +238,22 @@ STATIC_INLINE void jl_maybe_allocate_singleton_instance(jl_datatype_t *st) } } +int jl_datatype_isinlinealloc(jl_datatype_t *ty, int pointerfree) JL_NOTSAFEPOINT +{ + if (ty->name->mayinlinealloc && ty->layout) { + if (ty->layout->npointers > 0) { + if (pointerfree) + return 0; + if (ty->ninitialized != jl_svec_len(ty->types)) + return 0; + if (ty->layout->fielddesc_type > 1) // GC only implements support for 8 and 16 (not array32) + return 0; + } + return 1; + } + return 0; +} + static unsigned union_isinlinable(jl_value_t *ty, int pointerfree, size_t *nbytes, size_t *align, int asfield) JL_NOTSAFEPOINT { if (jl_is_uniontype(ty)) { @@ -249,7 +265,7 @@ static unsigned union_isinlinable(jl_value_t *ty, int pointerfree, size_t *nbyte return 0; return na + nb; } - if (jl_is_datatype(ty) && jl_datatype_isinlinealloc(ty) && (!pointerfree || ((jl_datatype_t*)ty)->layout->npointers == 0)) { + if (jl_is_datatype(ty) && jl_datatype_isinlinealloc((jl_datatype_t*)ty, pointerfree)) { size_t sz = jl_datatype_size(ty); size_t al = jl_datatype_align(ty); // primitive types in struct slots need their sizes aligned. issue #37974 @@ -329,15 +345,11 @@ void jl_compute_field_offsets(jl_datatype_t *st) const uint64_t max_offset = (((uint64_t)1) << 32) - 1; const uint64_t max_size = max_offset >> 1; - if (st->types == NULL || st->name->wrapper == NULL) - return; - if ((jl_is_tuple_type(st) || jl_is_namedtuple_type(st)) && !jl_is_concrete_type((jl_value_t*)st)) - return; + if (st->name->wrapper == NULL) + return; // we got called too early--we'll be back jl_datatype_t *w = (jl_datatype_t*)jl_unwrap_unionall(st->name->wrapper); - if (w->types == NULL) // we got called too early--we'll be back - return; + assert(st->types && w->types); size_t i, nfields = jl_svec_len(st->types); - int isinlinealloc = st->isconcretetype && !st->name->mutabl && !st->name->references_self; assert(st->ninitialized <= nfields); if (st == w && st->layout) { // this check allows us to force re-computation of the layout for some types during init @@ -386,17 +398,13 @@ void jl_compute_field_offsets(jl_datatype_t *st) st->has_concrete_subtype = !jl_is_datatype(fld) || ((jl_datatype_t *)fld)->has_concrete_subtype; } // compute layout for the wrapper object if the field types have no free variables - if (!st->isconcretetype) { - if (st != w) - return; // otherwise we would leak memory - for (i = 0; i < nfields; i++) { - if (jl_has_free_typevars(jl_field_type(st, i))) - return; // not worthwhile computing the rest - } + if (!st->isconcretetype && !jl_has_fixed_layout(st)) { + assert(st == w); // otherwise caller should not have requested this layout + return; } } - int isbitstype = isinlinealloc; + int isbitstype = st->isconcretetype && st->name->mayinlinealloc; for (i = 0; isbitstype && i < nfields; i++) { jl_value_t *fld = jl_field_type(st, i); isbitstype = jl_isbits(fld); @@ -513,14 +521,7 @@ void jl_compute_field_offsets(jl_datatype_t *st) } // now finish deciding if this instantiation qualifies for special properties assert(!isbitstype || st->layout->npointers == 0); // the definition of isbits - if (isinlinealloc && st->layout->npointers > 0) { - if (st->ninitialized != nfields) - isinlinealloc = 0; - else if (st->layout->fielddesc_type > 1) // GC only implements support for 8 and 16 (not array32) - isinlinealloc = 0; - } st->isbitstype = isbitstype; - st->isinlinealloc = isinlinealloc; jl_maybe_allocate_singleton_instance(st); return; } @@ -595,10 +596,12 @@ JL_DLLEXPORT jl_datatype_t *jl_new_datatype( t->name->wrapper = jl_new_struct(jl_unionall_type, jl_svecref(parameters, i), t->name->wrapper); jl_gc_wb(t->name, t->name->wrapper); } + if (!mutabl && !abstract && ftypes != NULL) + tn->mayinlinealloc = 1; } jl_precompute_memoized_dt(t, 0); - if (!abstract) + if (!abstract && t->types != NULL) jl_compute_field_offsets(t); JL_GC_POP(); @@ -615,7 +618,7 @@ JL_DLLEXPORT jl_datatype_t *jl_new_primitivetype(jl_value_t *name, jl_module_t * uint32_t alignm = next_power_of_two(nbytes); if (alignm > MAX_ALIGN) alignm = MAX_ALIGN; - bt->isbitstype = bt->isinlinealloc = (parameters == jl_emptysvec); + bt->isbitstype = (parameters == jl_emptysvec); bt->size = nbytes; bt->layout = jl_get_layout(0, 0, alignm, 0, NULL, NULL); bt->instance = NULL; diff --git a/src/dump.c b/src/dump.c index 8ce42de6f5f18..5d9924497a7c4 100644 --- a/src/dump.c +++ b/src/dump.c @@ -277,9 +277,8 @@ static void jl_serialize_datatype(jl_serializer_state *s, jl_datatype_t *dt) JL_ | (dt->isdispatchtuple << 2) | (dt->isbitstype << 3) | (dt->zeroinit << 4) - | (dt->isinlinealloc << 5) - | (dt->has_concrete_subtype << 6) - | (dt->cached_by_hash << 7)); + | (dt->has_concrete_subtype << 5) + | (dt->cached_by_hash << 6)); if (!dt->name->abstract) { write_uint16(s->s, dt->ninitialized); } @@ -816,7 +815,7 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_li jl_serialize_value(s, tn->wrapper); jl_serialize_value(s, tn->mt); ios_write(s->s, (char*)&tn->hash, sizeof(tn->hash)); - write_uint8(s->s, tn->abstract | (tn->mutabl << 1) | (tn->references_self << 2)); + write_uint8(s->s, tn->abstract | (tn->mutabl << 1) | (tn->references_self << 2) | (tn->mayinlinealloc << 3)); } return; } @@ -1277,9 +1276,8 @@ static jl_value_t *jl_deserialize_datatype(jl_serializer_state *s, int pos, jl_v dt->isdispatchtuple = (memflags >> 2) & 1; dt->isbitstype = (memflags >> 3) & 1; dt->zeroinit = (memflags >> 4) & 1; - dt->isinlinealloc = (memflags >> 5) & 1; - dt->has_concrete_subtype = (memflags >> 6) & 1; - dt->cached_by_hash = (memflags >> 7) & 1; + dt->has_concrete_subtype = (memflags >> 5) & 1; + dt->cached_by_hash = (memflags >> 6) & 1; if (abstract) dt->ninitialized = 0; else @@ -1737,6 +1735,7 @@ static jl_value_t *jl_deserialize_value_any(jl_serializer_state *s, uint8_t tag, tn->abstract = flags & 1; tn->mutabl = (flags>>1) & 1; tn->references_self = (flags>>2) & 1; + tn->mayinlinealloc = (flags>>3) & 1; } else { jl_datatype_t *dt = (jl_datatype_t*)jl_unwrap_unionall(jl_get_global(m, sym)); diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index deb174f700c77..173a9bb429c08 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -149,7 +149,7 @@ static Constant *julia_const_to_llvm(jl_codectx_t &ctx, const void *ptr, jl_data if (bt == jl_bool_type) return ConstantInt::get(T_int8, (*(const uint8_t*)ptr) ? 1 : 0); - Type *lt = julia_struct_to_llvm(ctx, (jl_value_t*)bt, NULL, NULL); + Type *lt = julia_struct_to_llvm(ctx, (jl_value_t*)bt, NULL); if (jl_is_vecelement_type((jl_value_t*)bt) && !jl_is_uniontype(jl_tparam0(bt))) bt = (jl_datatype_t*)jl_tparam0(bt); diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index eb9c91e301184..3bfe846c504a4 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -264,8 +264,6 @@ int jl_compile_extern_c(void *llvmmod, void *p, void *sysimg, jl_value_t *declrt return success; } -bool jl_type_mappable_to_c(jl_value_t *ty); - // declare a C-callable entry point; called during code loading from the toplevel extern "C" JL_DLLEXPORT void jl_extern_c(jl_value_t *declrt, jl_tupletype_t *sigt) @@ -292,7 +290,7 @@ void jl_extern_c(jl_value_t *declrt, jl_tupletype_t *sigt) size_t i, nargs = jl_nparams(sigt); for (i = 1; i < nargs; i++) { jl_value_t *ati = jl_tparam(sigt, i); - if (!jl_is_concrete_type(ati) || jl_is_kind(ati)) + if (!jl_is_concrete_type(ati) || jl_is_kind(ati) || !jl_type_mappable_to_c(ati)) jl_error("@ccallable: argument types must be concrete"); } diff --git a/src/jltypes.c b/src/jltypes.c index f759173f2051c..b445a6ddff0d6 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -35,6 +35,45 @@ static int typeenv_has(jl_typeenv_t *env, jl_tvar_t *v) JL_NOTSAFEPOINT return 0; } +static int layout_uses_free_typevars(jl_value_t *v, jl_typeenv_t *env) +{ + if (jl_typeis(v, jl_tvar_type)) + return !typeenv_has(env, (jl_tvar_t*)v); + if (jl_is_uniontype(v)) + return layout_uses_free_typevars(((jl_uniontype_t*)v)->a, env) || + layout_uses_free_typevars(((jl_uniontype_t*)v)->b, env); + if (jl_is_vararg(v)) { + jl_vararg_t *vm = (jl_vararg_t*)v; + if (vm->T) { + if (layout_uses_free_typevars(vm->T, env)) + return 1; + if (vm->N && layout_uses_free_typevars(vm->N, env)) + return 1; + } + return 0; + } + if (jl_is_unionall(v)) { + jl_unionall_t *ua = (jl_unionall_t*)v; + jl_typeenv_t newenv = { ua->var, NULL, env }; + return layout_uses_free_typevars(ua->body, &newenv); + } + if (jl_is_datatype(v)) { + jl_datatype_t *dt = (jl_datatype_t*)v; + if (dt->layout || dt->isconcretetype || !dt->name->mayinlinealloc) + return 0; + jl_svec_t *types = jl_get_fieldtypes(dt); + size_t i, l = jl_svec_len(types); + for (i = 0; i < l; i++) { + jl_value_t *ft = jl_svecref(types, i); + if (layout_uses_free_typevars(ft, env)) { + // This might be inline-alloc, but we don't know the layout + return 1; + } + } + } + return 0; +} + static int has_free_typevars(jl_value_t *v, jl_typeenv_t *env) JL_NOTSAFEPOINT { if (jl_typeis(v, jl_tvar_type)) { @@ -180,6 +219,35 @@ JL_DLLEXPORT int jl_has_typevar_from_unionall(jl_value_t *t, jl_unionall_t *ua) return _jl_has_typevar_from_ua(t, ua, NULL); } +int jl_has_fixed_layout(jl_datatype_t *dt) +{ + if (dt->layout || dt->isconcretetype) + return 1; + if (jl_is_tuple_type(dt)) + return 0; // TODO: relax more? + jl_svec_t *types = jl_get_fieldtypes(dt); + size_t i, l = jl_svec_len(types); + for (i = 0; i < l; i++) { + jl_value_t *ft = jl_svecref(types, i); + if (layout_uses_free_typevars(ft, NULL)) { + // This might be inline-alloc, but we don't know the layout + return 0; + } + } + return 1; +} + +int jl_type_mappable_to_c(jl_value_t *ty) +{ + assert(!jl_is_typevar(ty) && jl_is_type(ty)); + if (jl_is_structtype(ty)) { + jl_datatype_t *jst = (jl_datatype_t*)ty; + return jst->layout || jl_has_fixed_layout(jst); + } + if (jl_is_tuple_type(jl_unwrap_unionall(ty))) + return 0; // TODO: relax some? + return 1; // as boxed or primitive +} // Return true for any type (Integer or Unsigned) that can fit in a // size_t and pass back value, else return false @@ -1409,7 +1477,7 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value if (jl_is_primitivetype(dt)) { ndt->size = dt->size; ndt->layout = dt->layout; - ndt->isbitstype = ndt->isinlinealloc = ndt->isconcretetype; + ndt->isbitstype = ndt->isconcretetype; } jl_datatype_t *primarydt = ((jl_datatype_t*)jl_unwrap_unionall(tn->wrapper)); @@ -1449,8 +1517,11 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value } // now publish the finished result + // XXX: if the stack was used, this will publish in the wrong order, + // leading to incorrect layouts and data races (#40050: the A{T} should be + // an isbitstype singleton of size 0) if (cacheable) { - if (!jl_is_primitivetype(dt) && ndt->types != NULL && !ndt->name->abstract) { + if (!jl_is_primitivetype(dt) && ndt->types != NULL && ndt->isconcretetype) { jl_compute_field_offsets(ndt); } jl_cache_type_(ndt); @@ -1886,7 +1957,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_datatype_type->name->wrapper = (jl_value_t*)jl_datatype_type; jl_datatype_type->super = (jl_datatype_t*)jl_type_type; jl_datatype_type->parameters = jl_emptysvec; - jl_datatype_type->name->names = jl_perm_symsvec(18, + jl_datatype_type->name->names = jl_perm_symsvec(17, "name", "super", "parameters", @@ -1902,10 +1973,9 @@ void jl_init_types(void) JL_GC_DISABLED "isdispatchtuple", "isbitstype", "zeroinit", - "isinlinealloc", "has_concrete_subtype", "cached_by_hash"); - jl_datatype_type->types = jl_svec(18, + jl_datatype_type->types = jl_svec(17, jl_typename_type, jl_datatype_type, jl_simplevector_type, @@ -1913,7 +1983,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, // instance jl_any_type, jl_any_type, jl_any_type, jl_any_type, // properties jl_any_type, jl_any_type, jl_any_type, jl_any_type, - jl_any_type, jl_any_type, jl_any_type, jl_any_type); + jl_any_type, jl_any_type, jl_any_type); jl_datatype_type->ninitialized = 3; jl_precompute_memoized_dt(jl_datatype_type, 1); @@ -1922,14 +1992,14 @@ void jl_init_types(void) JL_GC_DISABLED jl_typename_type->name->mt = jl_nonfunction_mt; jl_typename_type->super = jl_any_type; jl_typename_type->parameters = jl_emptysvec; - jl_typename_type->name->names = jl_perm_symsvec(12, "name", "module", + jl_typename_type->name->names = jl_perm_symsvec(13, "name", "module", "names", "wrapper", "cache", "linearcache", - "hash", "abstract", "mutable", "references_self", + "hash", "abstract", "mutable", "references_self", "mayinlinealloc", "mt", "partial"); - jl_typename_type->types = jl_svec(12, jl_symbol_type, jl_any_type, jl_simplevector_type, + jl_typename_type->types = jl_svec(13, jl_symbol_type, jl_any_type, jl_simplevector_type, jl_type_type, jl_simplevector_type, jl_simplevector_type, - jl_any_type, jl_any_type, jl_any_type, jl_any_type, + jl_any_type, jl_any_type, jl_any_type, jl_any_type, jl_any_type, jl_methtable_type, jl_any_type); jl_typename_type->ninitialized = 2; jl_precompute_memoized_dt(jl_typename_type, 1); @@ -2503,13 +2573,13 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_datatype_type->types, 14, jl_bool_type); jl_svecset(jl_datatype_type->types, 15, jl_bool_type); jl_svecset(jl_datatype_type->types, 16, jl_bool_type); - jl_svecset(jl_datatype_type->types, 17, jl_bool_type); jl_svecset(jl_typename_type->types, 1, jl_module_type); jl_svecset(jl_typename_type->types, 3, jl_type_type); jl_svecset(jl_typename_type->types, 6, jl_long_type); jl_svecset(jl_typename_type->types, 7, jl_bool_type); jl_svecset(jl_typename_type->types, 8, jl_bool_type); jl_svecset(jl_typename_type->types, 9, jl_bool_type); + jl_svecset(jl_typename_type->types, 10, jl_bool_type); jl_svecset(jl_methtable_type->types, 4, jl_long_type); jl_svecset(jl_methtable_type->types, 6, jl_module_type); jl_svecset(jl_methtable_type->types, 7, jl_array_any_type); @@ -2540,10 +2610,10 @@ void jl_init_types(void) JL_GC_DISABLED jl_compute_field_offsets(jl_symbol_type); // override the preferred layout for a couple types - jl_lineinfonode_type->isinlinealloc = 0; // FIXME: assumed to be a pointer by codegen + jl_lineinfonode_type->name->mayinlinealloc = 0; // FIXME: assumed to be a pointer by codegen // It seems like we probably usually end up needing the box for kinds (used in an Any context)--but is that true? - jl_uniontype_type->isinlinealloc = 0; - jl_unionall_type->isinlinealloc = 0; + jl_uniontype_type->name->mayinlinealloc = 0; + jl_unionall_type->name->mayinlinealloc = 0; } #ifdef __cplusplus diff --git a/src/julia.h b/src/julia.h index cd3780b9eb453..1d9d735c10d06 100644 --- a/src/julia.h +++ b/src/julia.h @@ -432,6 +432,7 @@ typedef struct { uint8_t abstract; uint8_t mutabl; uint8_t references_self; + uint8_t mayinlinealloc; struct _jl_methtable_t *mt; jl_array_t *partial; // incomplete instantiations of this type } jl_typename_t; @@ -498,7 +499,6 @@ typedef struct _jl_datatype_t { uint8_t isdispatchtuple; // aka isleaftupletype uint8_t isbitstype; // relevant query for C-api and type-parameters uint8_t zeroinit; // if one or more fields requires zero-initialization - uint8_t isinlinealloc; // if this is allocated inline uint8_t has_concrete_subtype; // If clear, no value will have this datatype uint8_t cached_by_hash; // stored in hash-based set cache (instead of linear cache) } jl_datatype_t; @@ -1048,7 +1048,6 @@ STATIC_INLINE jl_value_t *jl_field_type_concrete(jl_datatype_t *st JL_PROPAGATES #define jl_datatype_align(t) (((jl_datatype_t*)t)->layout->alignment) #define jl_datatype_nbits(t) ((((jl_datatype_t*)t)->size)*8) #define jl_datatype_nfields(t) (((jl_datatype_t*)(t))->layout->nfields) -#define jl_datatype_isinlinealloc(t) (((jl_datatype_t *)(t))->isinlinealloc) JL_DLLEXPORT void *jl_symbol_name(jl_sym_t *s); // inline version with strong type check to detect typos in a `->name` chain diff --git a/src/julia_internal.h b/src/julia_internal.h index d6170a9d56719..275cadfc2cce2 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -499,6 +499,8 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_args_t fptr) JL_GC_DISABLED; int jl_obviously_unequal(jl_value_t *a, jl_value_t *b); JL_DLLEXPORT jl_array_t *jl_find_free_typevars(jl_value_t *v); +int jl_has_fixed_layout(jl_datatype_t *t); +int jl_type_mappable_to_c(jl_value_t *ty); jl_svec_t *jl_outer_unionall_vars(jl_value_t *u); jl_value_t *jl_type_intersection_env_s(jl_value_t *a, jl_value_t *b, jl_svec_t **penv, int *issubty); jl_value_t *jl_type_intersection_env(jl_value_t *a, jl_value_t *b, jl_svec_t **penv); @@ -532,6 +534,7 @@ void jl_foreach_reachable_mtable(void (*visit)(jl_methtable_t *mt, void *env), v void jl_init_main_module(void); int jl_is_submodule(jl_module_t *child, jl_module_t *parent) JL_NOTSAFEPOINT; jl_array_t *jl_get_loaded_modules(void); +int jl_datatype_isinlinealloc(jl_datatype_t *ty, int pointerfree); jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int expanded); diff --git a/src/method.c b/src/method.c index 1d3a593e638ed..b972d98ef1260 100644 --- a/src/method.c +++ b/src/method.c @@ -21,6 +21,25 @@ extern jl_value_t *jl_builtin_tuple; jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name, jl_value_t *nargs, jl_value_t *functionloc, jl_code_info_t *ci); +static void check_c_types(const char *where, jl_value_t *rt, jl_value_t *at) +{ + if (jl_is_svec(rt)) + jl_errorf("%s: missing return type", where); + JL_TYPECHKS(where, type, rt); + if (!jl_type_mappable_to_c(rt)) + jl_errorf("%s: return type doesn't correspond to a C type", where); + JL_TYPECHKS(where, simplevector, at); + int i, l = jl_svec_len(at); + for (i = 0; i < l; i++) { + jl_value_t *ati = jl_svecref(at, i); + if (jl_is_vararg(ati)) + jl_errorf("%s: Vararg not allowed for argument list", where); + JL_TYPECHKS(where, type, ati); + if (!jl_type_mappable_to_c(ati)) + jl_errorf("%s: argument %d type doesn't correspond to a C type", where, i + 1); + } +} + // Resolve references to non-locally-defined variables to become references to global // variables in `module` (unless the rvalue is one of the type parameters in `sparam_vals`). static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_svec_t *sparam_vals, @@ -120,10 +139,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve } jl_exprargset(e, 3, at); } - if (jl_is_svec(rt)) - jl_error("cfunction: missing return type"); - JL_TYPECHK(cfunction method definition, type, rt); - JL_TYPECHK(cfunction method definition, simplevector, at); + check_c_types("cfunction method definition", rt, at); JL_TYPECHK(cfunction method definition, quotenode, jl_exprarg(e, 4)); JL_TYPECHK(cfunction method definition, symbol, *(jl_value_t**)jl_exprarg(e, 4)); return expr; @@ -156,10 +172,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve } jl_exprargset(e, 2, at); } - if (jl_is_svec(rt)) - jl_error("ccall: missing return type"); - JL_TYPECHK(ccall method definition, type, rt); - JL_TYPECHK(ccall method definition, simplevector, at); + check_c_types("ccall method definition", rt, at); JL_TYPECHK(ccall method definition, long, jl_exprarg(e, 3)); JL_TYPECHK(ccall method definition, quotenode, jl_exprarg(e, 4)); JL_TYPECHK(ccall method definition, symbol, *(jl_value_t**)jl_exprarg(e, 4)); diff --git a/stdlib/LibGit2/src/types.jl b/stdlib/LibGit2/src/types.jl index 129f526812926..9ffcaa3646127 100644 --- a/stdlib/LibGit2/src/types.jl +++ b/stdlib/LibGit2/src/types.jl @@ -192,7 +192,7 @@ The fields represent: perfdata_cb::Ptr{Cvoid} = C_NULL perfdata_payload::Any = Nothing end -@assert CheckoutOptions.isinlinealloc +@assert Base.allocatedinline(CheckoutOptions) """ LibGit2.TransferProgress @@ -209,7 +209,7 @@ Matches the [`git_indexer_progress`](https://libgit2.org/libgit2/#HEAD/type/git_ indexed_deltas::Cuint = Cuint(0) received_bytes::Csize_t = Csize_t(0) end -@assert TransferProgress.isinlinealloc +@assert Base.allocatedinline(TransferProgress) """ LibGit2.RemoteCallbacks @@ -235,7 +235,7 @@ Matches the [`git_remote_callbacks`](https://libgit2.org/libgit2/#HEAD/type/git_ resolve_url::Ptr{Cvoid} = C_NULL end end -@assert RemoteCallbacks.isinlinealloc +@assert Base.allocatedinline(RemoteCallbacks) """ LibGit2.Callbacks @@ -313,7 +313,7 @@ julia> fetch(remote, "master", options=fo) certificate_cb::Ptr{Cvoid} = certificate_cb() payload::Any = nothing end -@assert ProxyOptions.isinlinealloc +@assert Base.allocatedinline(ProxyOptions) """ LibGit2.FetchOptions @@ -347,7 +347,7 @@ The fields represent: custom_headers::StrArrayStruct = StrArrayStruct() end end -@assert FetchOptions.isinlinealloc +@assert Base.allocatedinline(FetchOptions) """ @@ -384,7 +384,7 @@ The fields represent: remote_cb::Ptr{Cvoid} = C_NULL remote_cb_payload::Any = nothing end -@assert CloneOptions.isinlinealloc +@assert Base.allocatedinline(CloneOptions) """ LibGit2.DiffOptionsStruct @@ -438,7 +438,7 @@ The fields represent: old_prefix::Cstring = Cstring(C_NULL) new_prefix::Cstring = Cstring(C_NULL) end -@assert DiffOptionsStruct.isinlinealloc +@assert Base.allocatedinline(DiffOptionsStruct) """ LibGit2.DescribeOptions @@ -468,7 +468,7 @@ The fields represent: only_follow_first_parent::Cint = Cint(0) show_commit_oid_as_fallback::Cint = Cint(0) end -@assert DescribeOptions.isinlinealloc +@assert Base.allocatedinline(DescribeOptions) """ LibGit2.DescribeFormatOptions @@ -487,7 +487,7 @@ The fields represent: always_use_long_format::Cint = Cint(0) dirty_suffix::Cstring = Cstring(C_NULL) end -@assert DescribeFormatOptions.isinlinealloc +@assert Base.allocatedinline(DescribeFormatOptions) """ LibGit2.DiffFile @@ -617,7 +617,7 @@ The fields represent: file_favor::GIT_MERGE_FILE_FAVOR = Consts.MERGE_FILE_FAVOR_NORMAL file_flags::GIT_MERGE_FILE = Consts.MERGE_FILE_DEFAULT end -@assert MergeOptions.isinlinealloc +@assert Base.allocatedinline(MergeOptions) """ LibGit2.BlameOptions @@ -647,7 +647,7 @@ The fields represent: min_line::Csize_t = Csize_t(1) max_line::Csize_t = Csize_t(0) end -@assert BlameOptions.isinlinealloc +@assert Base.allocatedinline(BlameOptions) """ @@ -678,7 +678,7 @@ The fields represent: custom_headers::StrArrayStruct = StrArrayStruct() end end -@assert PushOptions.isinlinealloc +@assert Base.allocatedinline(PushOptions) """ @@ -701,7 +701,7 @@ The fields represent: merge_opts::MergeOptions = MergeOptions() checkout_opts::CheckoutOptions = CheckoutOptions() end -@assert CherrypickOptions.isinlinealloc +@assert Base.allocatedinline(CherrypickOptions) """ @@ -771,7 +771,7 @@ The fields represent: end checkout_opts::CheckoutOptions = CheckoutOptions() end -@assert RebaseOptions.isinlinealloc +@assert Base.allocatedinline(RebaseOptions) """ LibGit2.RebaseOperation @@ -834,7 +834,7 @@ The fields represent: baseline::Ptr{Cvoid} = C_NULL end end -@assert StatusOptions.isinlinealloc +@assert Base.allocatedinline(StatusOptions) """ LibGit2.StatusEntry @@ -902,7 +902,7 @@ Matches the [`git_config_entry`](https://libgit2.org/libgit2/#HEAD/type/git_conf free::Ptr{Cvoid} = C_NULL payload::Any = nothing end -@assert ConfigEntry.isinlinealloc +@assert Base.allocatedinline(ConfigEntry) function Base.show(io::IO, ce::ConfigEntry) print(io, "ConfigEntry(\"", unsafe_string(ce.name), "\", \"", unsafe_string(ce.value), "\")") @@ -1136,7 +1136,7 @@ The fields represent: boundary::Char = '\0' end -@assert BlameHunk.isinlinealloc +@assert Base.allocatedinline(BlameHunk) """ with(f::Function, obj) diff --git a/test/ccall.jl b/test/ccall.jl index 77e17c6843db1..ae29b40784eb6 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -1443,15 +1443,28 @@ end eval(:(f20835(x) = ccall(:fn, Cvoid, (Ptr{typeof(x)},), x)))) @test_throws(UndefVarError(:Something_not_defined_20835), eval(:(f20835(x) = ccall(:fn, Something_not_defined_20835, (Ptr{typeof(x)},), x)))) - -@noinline f21104at(::Type{T}) where {T} = ccall(:fn, Cvoid, (Some{T},), Some(0)) -@noinline f21104rt(::Type{T}) where {T} = ccall(:fn, Some{T}, ()) -@test code_llvm(devnull, f21104at, (Type{Float64},)) === nothing -@test code_llvm(devnull, f21104rt, (Type{Float64},)) === nothing -@test_throws(ErrorException("ccall argument 1 doesn't correspond to a C type"), - f21104at(Float64)) -@test_throws(ErrorException("ccall return type doesn't correspond to a C type"), - f21104rt(Float64)) +@test isempty(methods(f20835)) + +@test_throws(ErrorException("ccall method definition: argument 1 type doesn't correspond to a C type"), + @eval f21104(::Type{T}) where {T} = ccall(:fn, Cvoid, (Some{T},), Some(0))) +@test_throws(ErrorException("ccall method definition: return type doesn't correspond to a C type"), + @eval f21104(::Type{T}) where {T} = ccall(:fn, Some{T}, ())) +@test isempty(methods(f21104)) +@test_throws(ErrorException("ccall method definition: argument 1 type doesn't correspond to a C type"), + @eval if false; ccall(:fn, Cvoid, (Some.body,), Some(0)); end) +@test_throws(ErrorException("ccall method definition: return type doesn't correspond to a C type"), + @eval if false; ccall(:fn, Some.body, ()); end) +@test_throws(ErrorException("ccall method definition: return type doesn't correspond to a C type"), + @eval if false; ccall(:fn, Tuple, ()); end) +## TODO: lowering is broken on this (throws "syntax: ssavalue with no def") +#@test_throws(ErrorException("ccall method definition: return type doesn't correspond to a C type"), +# @eval if false; ccall(:fn, Tuple{Val{T}} where T, ()); end) +@test_throws(ErrorException("ccall method definition: return type doesn't correspond to a C type"), + @eval if false; ccall(:fn, Tuple{Val}, ()); end) +@test_throws(TypeError, @eval if false; ccall(:fn, Some.var, ()); end) +@test_throws(TypeError, @eval if false; ccall(:fn, Cvoid, (Some.var,), Some(0)); end) +@test_throws(ErrorException("ccall method definition: Vararg not allowed for argument list"), + @eval ccall(+, Int, (Vararg{Int},), 1)) # test for malformed syntax errors @test Expr(:error, "more arguments than types for ccall") == Meta.lower(@__MODULE__, :(ccall(:fn, A, (), x))) @@ -1482,21 +1495,20 @@ end evalf_callback_19805(ci::callinfos_19805{FUNC_FT}) where {FUNC_FT} = ci.f(0.5)::Float64 -evalf_callback_c_19805(ci::callinfos_19805{FUNC_FT}) where {FUNC_FT} = @cfunction( - evalf_callback_19805, Float64, (callinfos_19805{FUNC_FT},)) - -@test_throws(ErrorException("cfunction argument 1 doesn't correspond to a C type"), - evalf_callback_c_19805( callinfos_19805(sin) )) -@test_throws(ErrorException("cfunction argument 2 doesn't correspond to a C type"), - @cfunction(+, Int, (Int, Nothing))) -@test_throws(ErrorException("cfunction: Vararg syntax not allowed for argument list"), - @cfunction(+, Int, (Vararg{Int},))) +@test_throws(ErrorException("cfunction method definition: argument 1 type doesn't correspond to a C type"), + @eval evalf_callback_c_19805(ci::callinfos_19805{FUNC_FT}) where {FUNC_FT} = + @cfunction(evalf_callback_19805, Float64, (callinfos_19805{FUNC_FT},))) +@test isempty(methods(evalf_callback_c_19805)) +@test_throws(ErrorException("cfunction method definition: Vararg not allowed for argument list"), + @eval if false; @cfunction(+, Int, (Vararg{Int},)); end) @test_throws(ErrorException("could not evaluate cfunction argument type (it might depend on a local variable)"), @eval () -> @cfunction(+, Int, (Ref{T}, Ref{T})) where T) @test_throws(ErrorException("could not evaluate cfunction return type (it might depend on a local variable)"), @eval () -> @cfunction(+, Ref{T}, (Int, Int)) where T) +@test_throws(ErrorException("cfunction argument 2 doesn't correspond to a C type"), + @eval @cfunction(+, Int, (Int, Nothing))) @test_throws(ErrorException("cfunction return type Ref{Any} is invalid. Use Any or Ptr{Any} instead."), - @cfunction(+, Ref{Any}, (Int, Int))) + @eval @cfunction(+, Ref{Any}, (Int, Int))) # test Ref{abstract_type} calling parameter passes a heap box abstract type Abstract22734 end