diff --git a/RELEASENOTES.docu b/RELEASENOTES.docu index 823c00217..5d608cea7 100644 --- a/RELEASENOTES.docu +++ b/RELEASENOTES.docu @@ -1784,4 +1784,12 @@ extern typedef struct { } my_type_t; attribute values . legacy_attributes will always be disabled with Simics API version 8 or above. + Typechecking has been reworked + to be more strict, and more accurately reject type mismatch that would + result in invalid C or GCC warnings . To + avoid breakage from novel type errors from the stricter typechecking, + which particularly affects pointer types, the legacy lenient typechecking + is still used by default with Simics API version 7 or below. The strict + typechecking can be enabled by passing + --no-compat=lenient_typechecking to DMLC. diff --git a/lib/1.2/dml-builtins.dml b/lib/1.2/dml-builtins.dml index 32cabd500..f2511de67 100644 --- a/lib/1.2/dml-builtins.dml +++ b/lib/1.2/dml-builtins.dml @@ -216,6 +216,7 @@ template device { parameter _compat_shared_logs_on_device auto; parameter _compat_suppress_WLOGMIXUP auto; parameter _compat_legacy_attributes auto; + parameter _compat_lenient_typechecking auto; parameter _compat_dml12_inline auto; parameter _compat_dml12_not auto; parameter _compat_dml12_goto auto; diff --git a/lib/1.2/simics-api.dml b/lib/1.2/simics-api.dml index 8f5e1933b..3c39141a9 100644 --- a/lib/1.2/simics-api.dml +++ b/lib/1.2/simics-api.dml @@ -48,15 +48,15 @@ extern uint16 CONVERT_BE16(uint16 val); extern uint32 CONVERT_BE32(uint32 val); extern uint64 CONVERT_BE64(uint64 val); -extern uint8 LOAD_LE8 (void *src); -extern uint16 LOAD_LE16(void *src); -extern uint32 LOAD_LE32(void *src); -extern uint64 LOAD_LE64(void *src); +extern uint8 LOAD_LE8 (const void *src); +extern uint16 LOAD_LE16(const void *src); +extern uint32 LOAD_LE32(const void *src); +extern uint64 LOAD_LE64(const void *src); -extern uint8 LOAD_BE8 (void *src); -extern uint16 LOAD_BE16(void *src); -extern uint32 LOAD_BE32(void *src); -extern uint64 LOAD_BE64(void *src); +extern uint8 LOAD_BE8 (const void *src); +extern uint16 LOAD_BE16(const void *src); +extern uint32 LOAD_BE32(const void *src); +extern uint64 LOAD_BE64(const void *src); extern void STORE_LE8 (void *dst, uint8 val); extern void STORE_LE16(void *dst, uint16 val); @@ -68,15 +68,15 @@ extern void STORE_BE16(void *dst, uint16 val); extern void STORE_BE32(void *dst, uint32 val); extern void STORE_BE64(void *dst, uint64 val); -extern uint8 UNALIGNED_LOAD_LE8 (void *src); -extern uint16 UNALIGNED_LOAD_LE16(void *src); -extern uint32 UNALIGNED_LOAD_LE32(void *src); -extern uint64 UNALIGNED_LOAD_LE64(void *src); +extern uint8 UNALIGNED_LOAD_LE8 (const void *src); +extern uint16 UNALIGNED_LOAD_LE16(const void *src); +extern uint32 UNALIGNED_LOAD_LE32(const void *src); +extern uint64 UNALIGNED_LOAD_LE64(const void *src); -extern uint8 UNALIGNED_LOAD_BE8 (void *src); -extern uint16 UNALIGNED_LOAD_BE16(void *src); -extern uint32 UNALIGNED_LOAD_BE32(void *src); -extern uint64 UNALIGNED_LOAD_BE64(void *src); +extern uint8 UNALIGNED_LOAD_BE8 (const void *src); +extern uint16 UNALIGNED_LOAD_BE16(const void *src); +extern uint32 UNALIGNED_LOAD_BE32(const void *src); +extern uint64 UNALIGNED_LOAD_BE64(const void *src); extern void UNALIGNED_STORE_LE8 (void *dst, uint8 val); extern void UNALIGNED_STORE_LE16(void *dst, uint16 val); diff --git a/lib/1.4/dml-builtins.dml b/lib/1.4/dml-builtins.dml index e1514f804..fc1af1bf3 100644 --- a/lib/1.4/dml-builtins.dml +++ b/lib/1.4/dml-builtins.dml @@ -562,6 +562,7 @@ template device { param _compat_shared_logs_on_device auto; param _compat_suppress_WLOGMIXUP auto; param _compat_legacy_attributes auto; + param _compat_lenient_typechecking auto; param _compat_dml12_inline auto; param _compat_dml12_not auto; param _compat_dml12_goto auto; diff --git a/lib/1.4/internal.dml b/lib/1.4/internal.dml index 80c4f57d6..a5f5c8562 100644 --- a/lib/1.4/internal.dml +++ b/lib/1.4/internal.dml @@ -39,15 +39,15 @@ extern uint16 CONVERT_BE16(uint16 val); extern uint32 CONVERT_BE32(uint32 val); extern uint64 CONVERT_BE64(uint64 val); -extern uint8 LOAD_LE8 (void *src); -extern uint16 LOAD_LE16(void *src); -extern uint32 LOAD_LE32(void *src); -extern uint64 LOAD_LE64(void *src); +extern uint8 LOAD_LE8 (const void *src); +extern uint16 LOAD_LE16(const void *src); +extern uint32 LOAD_LE32(const void *src); +extern uint64 LOAD_LE64(const void *src); -extern uint8 LOAD_BE8 (void *src); -extern uint16 LOAD_BE16(void *src); -extern uint32 LOAD_BE32(void *src); -extern uint64 LOAD_BE64(void *src); +extern uint8 LOAD_BE8 (const void *src); +extern uint16 LOAD_BE16(const void *src); +extern uint32 LOAD_BE32(const void *src); +extern uint64 LOAD_BE64(const void *src); extern void STORE_LE8 (void *dst, uint8 val); extern void STORE_LE16(void *dst, uint16 val); @@ -59,15 +59,15 @@ extern void STORE_BE16(void *dst, uint16 val); extern void STORE_BE32(void *dst, uint32 val); extern void STORE_BE64(void *dst, uint64 val); -extern uint8 UNALIGNED_LOAD_LE8 (void *src); -extern uint16 UNALIGNED_LOAD_LE16(void *src); -extern uint32 UNALIGNED_LOAD_LE32(void *src); -extern uint64 UNALIGNED_LOAD_LE64(void *src); +extern uint8 UNALIGNED_LOAD_LE8 (const void *src); +extern uint16 UNALIGNED_LOAD_LE16(const void *src); +extern uint32 UNALIGNED_LOAD_LE32(const void *src); +extern uint64 UNALIGNED_LOAD_LE64(const void *src); -extern uint8 UNALIGNED_LOAD_BE8 (void *src); -extern uint16 UNALIGNED_LOAD_BE16(void *src); -extern uint32 UNALIGNED_LOAD_BE32(void *src); -extern uint64 UNALIGNED_LOAD_BE64(void *src); +extern uint8 UNALIGNED_LOAD_BE8 (const void *src); +extern uint16 UNALIGNED_LOAD_BE16(const void *src); +extern uint32 UNALIGNED_LOAD_BE32(const void *src); +extern uint64 UNALIGNED_LOAD_BE64(const void *src); extern void UNALIGNED_STORE_LE8 (void *dst, uint8 val); extern void UNALIGNED_STORE_LE16(void *dst, uint16 val); diff --git a/py/dml/c_backend.py b/py/dml/c_backend.py index 56b74ac37..c4bdbc764 100644 --- a/py/dml/c_backend.py +++ b/py/dml/c_backend.py @@ -805,13 +805,13 @@ def generate_implement_method(device, ifacestruct, meth, indices): # method in DML raise EMETH(meth.site, None, 'interface method is variadic') for ((mp, mt), it) in zip(meth.inp, iface_input_types): - if safe_realtype(mt).cmp(safe_realtype(it)) != 0: + if not safe_realtype_unconst(mt).eq(safe_realtype_unconst(it)): raise EARGT(meth.site, 'implement', meth.name, mt, mp, it, 'method') if iface_num_outputs and dml.globals.dml_version != (1, 2): [(_, mt)] = meth.outp - if safe_realtype(mt).cmp( - safe_realtype(func_type.output_type)) != 0: + if not safe_realtype_unconst(mt).eq( + safe_realtype_unconst(func_type.output_type)): raise EARGT(meth.site, 'implement', meth.name, mt, '', func_type.output_type, 'method') diff --git a/py/dml/clone_test.py b/py/dml/clone_test.py deleted file mode 100644 index 76f7760ef..000000000 --- a/py/dml/clone_test.py +++ /dev/null @@ -1,45 +0,0 @@ -# © 2021 Intel Corporation -# SPDX-License-Identifier: MPL-2.0 - -import dml.types as dt -import dml.globals -from dml import ctree -from dml import logging -from dml import types - -import unittest - -class TestClone(unittest.TestCase): - def test(self): - # types which support clone - typ0 = dt.TVoid() - for typ in (dt.TVoid(), - dt.TNamed("int"), - dt.TBool(), - dt.TInt(8, False, {}), - dt.TEndianInt(8, False, 'big-endian', {}), - dt.TFloat("a"), - dt.TArray(typ0, ctree.mkIntegerLiteral(0, 2)), - dt.TPtr(typ0), - dt.TVector(typ0), - dt.TTrait(object()), - dt.TStruct({"name": types.TInt(32, False)}), - dt.TLayout("big-endian", {}), - dt.TDevice("a")): - typ_clone = typ.clone() - self.assertEqual( - types.realtype(typ_clone).cmp(types.realtype(typ)), 0) - self.assertEqual( - types.realtype(typ).cmp(types.realtype(typ_clone)), 0) - typ_clone.const = True - self.assertEqual(typ.const, False) - # special case for TraitList, because realtype requires global - # state (typedefs) - typ = dt.TTraitList("a") - typ_clone = typ.clone() - self.assertEqual(typ.cmp(typ_clone), 0) - self.assertEqual(typ_clone.cmp(typ), 0) - dml.globals.dml_version = (1, 2) - # types which do not support clone - with self.assertRaises(logging.ICE): - dt.TUnknown().clone() diff --git a/py/dml/codegen.py b/py/dml/codegen.py index 1fe97b5a2..c7d1b9019 100644 --- a/py/dml/codegen.py +++ b/py/dml/codegen.py @@ -1306,18 +1306,7 @@ def expr_cast(tree, location, scope): for (site, _) in struct_defs: report(EANONSTRUCT(site, "'cast' expression")) - if (compat.dml12_misc in dml.globals.enabled_compat - and isinstance(expr, InterfaceMethodRef)): - # Workaround for SIMICS-9868 - return mkLit(tree.site, "%s->%s" % ( - expr.node_expr.read(), expr.method_name), type) - - if isinstance(expr, NonValue) and ( - not isinstance(expr, NodeRef) - or not isinstance(safe_realtype(type), TTrait)): - raise expr.exc() - else: - return mkCast(tree.site, expr, type) + return mkCast(tree.site, expr, type) @expression_dispatcher def expr_undefined(tree, location, scope): @@ -1518,7 +1507,7 @@ def eval_type(asttype, site, location, scope, extern=False, typename=None, raise EBFLD(fsite, "field %s has wrong size" % name) members[name] = (mtype, msb, lsb) - etype = TInt(width, False, members) + etype = TInt(width, False, members, label=typename) elif tag == 'typeof': expr = codegen_expression_maybe_nonvalue(info, location, scope) if isinstance(expr, NonValue): @@ -2008,9 +1997,8 @@ def mk_sym(name, typ, mkunique=not dml.globals.debuggable): for (name, typ) in decls: sym = mk_sym(name, typ) tgt_typ = safe_realtype_shallow(typ) - if tgt_typ.const: - nonconst_typ = tgt_typ.clone() - nonconst_typ.const = False + if shallow_const(tgt_typ): + nonconst_typ = safe_realtype_unconst(tgt_typ) tgt_sym = mk_sym('_tmp_' + name, nonconst_typ, True) sym.init = ExpressionInitializer(mkLocalVariable(stmt.site, tgt_sym)) diff --git a/py/dml/compat.py b/py/dml/compat.py index dbc776fc3..b8945d555 100644 --- a/py/dml/compat.py +++ b/py/dml/compat.py @@ -216,6 +216,29 @@ class legacy_attributes(CompatFeature): + "dictionary type ('D')") last_api_version = api_7 +@feature +class lenient_typechecking(CompatFeature): + '''This compatibility feature makes DMLC's type checking very inexact and + lenient in multiple respects when compared to GCC's type checking of the + generated C. + This discrepency mostly affects method overrides or uses of `extern`:d C + macros, because in those scenarios DMLC can become wholly responsible for + proper type checking. + + While migrating away from this feature, the most common type errors that + its disablement introduces are due to discrepencies between pointer + types. In particular, implicitly discarding `const`-qualification of a + pointer's base type will never be tolerated, and `void` pointers are only + considered equivalent with any other pointer type in the same contexts as + C. + + Novel type errors from uses of `extern`:d macros can often be resolved by + changing the signature of the `extern` declaration to more accurately + reflect the macro's effective type. + ''' + short = "Make type checking inexact and lenient" + last_api_version = api_7 + @feature class dml12_inline(CompatFeature): '''When using `inline` to inline a method in a DML 1.2 device, diff --git a/py/dml/ctree.py b/py/dml/ctree.py index 2376f3f4f..d5e7a68d4 100644 --- a/py/dml/ctree.py +++ b/py/dml/ctree.py @@ -1135,7 +1135,7 @@ def as_int(e): if t.is_endian: (fun, funtype) = t.get_load_fun() e = dml.expr.Apply(e.site, mkLit(e.site, fun, funtype), (e,), funtype) - if not compatible_types(realtype(e.ctype()), target_type): + if not realtype(e.ctype()).eq(target_type): e = mkCast(e.site, e, target_type) return e else: @@ -1203,7 +1203,7 @@ def mkIfExpr(site, cond, texpr, fexpr): # Normally handled by expr_conditional; this only happens # in DMLC-internal mkIfExpr calls (result, rtype) = (texpr, ttype) if cond.value else (fexpr, ftype) - assert rtype.cmp(utype) == 0 + assert rtype.eq(utype) return result return IfExpr(site, cond, texpr, fexpr, utype) else: @@ -1222,19 +1222,23 @@ def mkIfExpr(site, cond, texpr, fexpr): (texpr, fexpr, utype) = usual_int_conv( texpr, ttype, fexpr, ftype) else: - if not compatible_types(ttype, ftype): - raise EBINOP(site, ':', texpr, fexpr) - # TODO: in C, the rules are more complex, - # but our type system is too primitive to cover that if (isinstance(ttype, (TPtr, TArray)) - and isinstance(ftype, (TPtr, TArray))): + and isinstance(ftype, (TPtr, TArray)) + and (ttype.base.void or ftype.base.void + or safe_realtype_unconst(ttype.base).eq( + safe_realtype_unconst(ftype.base)))): # if any branch is void, then the union is too base = (ftype if ftype.base.void else ttype).base.clone() # if any branch is const *, then the union is too - base.const = ttype.base.const or ftype.base.const + base.const = ((isinstance(ttype, TArray) and ttype.const) + or (isinstance(ftype, TArray) and ftype.const) + or shallow_const(ttype.base) + or shallow_const(ftype.base)) utype = TPtr(base) - else: + elif safe_realtype_unconst(ttype).eq(safe_realtype_unconst(ftype)): utype = ttype + else: + raise EBINOP(site, ':', texpr, fexpr) if cond.constant: # should be safe: texpr and fexpr now have compatible types return texpr if cond.value else fexpr @@ -1396,7 +1400,8 @@ def make(cls, site, lh, rh): if ((lhtype.is_arith and rhtype.is_arith) or (isinstance(lhtype, (TPtr, TArray)) and isinstance(rhtype, (TPtr, TArray)) - and compatible_types(lhtype.base, rhtype.base))): + and safe_realtype_unconst(lhtype.base).eq( + safe_realtype_unconst(rhtype.base)))): return cls.make_simple(site, lh, rh) raise EILLCOMP(site, lh, lhtype, rh, rhtype) @@ -1559,7 +1564,7 @@ def make(cls, site, lh, rh): return mkBoolConstant(site, (lhc.node is rhc.node and lhc_indices == rhc_indices)) if (isinstance(lhc, HookRef) and isinstance(rhc, HookRef) - and lhtype.cmp(rhtype) == 0): + and lhtype.eq(rhtype)): lh_indices = [idx.value for idx in lhc.indices] rh_indices = [idx.value for idx in rhc.indices] return mkBoolConstant(site, (lhc.hook is rhc.hook @@ -1601,7 +1606,9 @@ def make(cls, site, lh, rh): if ((lhtype.is_arith and rhtype.is_arith) or (isinstance(lhtype, (TPtr, TArray)) and isinstance(rhtype, (TPtr, TArray)) - and compatible_types(lhtype, rhtype)) + and (lhtype.base.void or rhtype.base.void + or safe_realtype_unconst(lhtype.base).eq( + safe_realtype_unconst(rhtype.base)))) or (isinstance(lhtype, TBool) and isinstance(rhtype, TBool))): return Equals(site, lh, rh) @@ -1610,7 +1617,7 @@ def make(cls, site, lh, rh): return IdentityEq(site, TraitObjIdentity(lh.site, lh), TraitObjIdentity(rh.site, rh)) if (isinstance(lhtype, THook) and isinstance(rhtype, THook) - and lhtype.cmp(rhtype) == 0): + and lhtype.eq(rhtype)): return IdentityEq(site, lh, rh) raise EILLCOMP(site, lh, lhtype, rh, rhtype) @@ -2932,8 +2939,8 @@ def mkInterfaceMethodRef(site, iface_node, indices, method_name): if (not isinstance(ftype, TPtr) or not isinstance(ftype.base, TFunction) or not ftype.base.input_types - or TPtr(safe_realtype(TNamed('conf_object_t'))).cmp( - safe_realtype(ftype.base.input_types[0])) != 0): + or not TPtr(safe_realtype_unconst(TNamed('conf_object_t'))).eq( + safe_realtype_unconst(ftype.base.input_types[0]))): # non-method members are not accessible raise EMEMBER(site, struct_name, method_name) @@ -4684,7 +4691,10 @@ class ArrayRef(LValue): explicit_type = True @auto_init def __init__(self, site, expr, idx): - self.type = realtype_shallow(expr.ctype()).base + expr_type = realtype_shallow(expr.ctype()) + self.type = conv_const(expr_type.const + and isinstance(expr_type, TArray), + expr_type.base) def __str__(self): return '%s[%s]' % (self.expr, self.idx) def read(self): @@ -4797,6 +4807,15 @@ def mkCast(site, expr, new_type): raise ETEMPLATEUPCAST(site, "object", new_type) else: return mkTraitUpcast(site, expr, real.trait) + + if (compat.dml12_misc in dml.globals.enabled_compat + and isinstance(expr, InterfaceMethodRef)): + # Workaround for SIMICS-9868 + return mkLit(site, "%s->%s" % ( + expr.node_expr.read(), expr.method_name), new_type) + + if isinstance(expr, NonValue): + raise expr.exc() old_type = safe_realtype(expr.ctype()) if (dml.globals.compat_dml12_int(site) and (isinstance(old_type, (TStruct, TVector)) @@ -4804,15 +4823,17 @@ def mkCast(site, expr, new_type): # these casts are permitted by C only if old and new are # the same type, which is useless return Cast(site, expr, new_type) - if isinstance(real, TStruct): - if isinstance(old_type, TStruct) and old_type.label == real.label: - return expr + if isinstance(real, (TVoid, TArray, TFunction)): raise ECAST(site, expr, new_type) - if isinstance(real, TExternStruct): - if isinstance(old_type, TExternStruct) and old_type.id == real.id: - return expr - raise ECAST(site, expr, new_type) - if isinstance(real, (TVoid, TArray, TVector, TTraitList, TFunction)): + if old_type.eq(real): + if (dml.globals.compat_dml12_int(expr.site) + and old_type.is_int + and not old_type.is_endian): + # 1.2 integer expressions often lie about their actual type, + # and require a "redundant" cast! Why yes, this IS horrid! + return Cast(site, expr, new_type) + return mkRValue(expr) + if isinstance(real, (TStruct, TExternStruct, TVector, TTraitList)): raise ECAST(site, expr, new_type) if isinstance(old_type, (TVoid, TStruct, TVector, TTraitList, TTrait)): raise ECAST(site, expr, new_type) @@ -4820,7 +4841,7 @@ def mkCast(site, expr, new_type): expr = as_int(expr) old_type = safe_realtype(expr.ctype()) if real.is_int and not real.is_endian: - if isinstance(expr, IntegerConstant): + if old_type.is_int and expr.constant: value = truncate_int_bits(expr.value, real.signed, real.bits) if dml.globals.compat_dml12_int(site): return IntegerConstant_dml12(site, value, real) @@ -4831,8 +4852,8 @@ def mkCast(site, expr, new_type): # Shorten redundant chains of integer casts. Avoids insane C # output for expressions like a+b+c+d. if (isinstance(expr, Cast) - and isinstance(expr.type, TInt) - and expr.type.bits >= real.bits): + and isinstance(old_type, TInt) + and old_type.bits >= real.bits): # (uint64)(int64)x -> (uint64)x expr = expr.expr old_type = safe_realtype(expr.ctype()) @@ -4868,9 +4889,7 @@ def mkCast(site, expr, new_type): return expr elif real.is_int and real.is_endian: old_type = safe_realtype(expr.ctype()) - if real.cmp(old_type) == 0: - return expr - elif old_type.is_arith or isinstance(old_type, TPtr): + if old_type.is_arith or isinstance(old_type, TPtr): return mkApply( expr.site, mkLit(expr.site, *real.get_store_fun()), @@ -4927,7 +4946,6 @@ def mkCast(site, expr, new_type): class RValue(Expression): '''Wraps an lvalue to prohibit write. Useful when a composite expression is reduced down to a single variable.''' - writable = False @auto_init def __init__(self, site, expr): pass def __str__(self): @@ -4936,11 +4954,22 @@ def ctype(self): return self.expr.ctype() def read(self): return self.expr.read() - def discard(self): pass + def discard(self): + return self.expr.discard() def incref(self): self.expr.incref() def decref(self): self.expr.decref() + @property + def explicit_type(self): + return self.expr.explicit_type + @property + def type(self): + assert self.explicit_type + return self.expr.type + @property + def is_pointer_to_stack_allocation(self): + return self.expr.is_pointer_to_stack_allocation def mkRValue(expr): if isinstance(expr, LValue) or expr.writable: diff --git a/py/dml/ctree_test.py b/py/dml/ctree_test.py index bc494f89e..4d9defd3f 100644 --- a/py/dml/ctree_test.py +++ b/py/dml/ctree_test.py @@ -284,8 +284,8 @@ def framework_assertions(self): @subtest() def long_types(self): def tcmp(t1, t2): - left = t1.cmp(t2) - right = t2.cmp(t1) + left = t1.eq(t2) + right = t2.eq(t1) self.assertEqual(left, right) return left u = types.TLong(False) @@ -301,8 +301,7 @@ def tcmp(t1, t2): # incompatible for t1 in int_types: for t2 in int_types: - self.assertEqual(tcmp(t1, t2), - 0 if t1 is t2 else NotImplemented, (t1, t2)) + self.assertEqual(tcmp(t1, t2), t1 is t2, (t1, t2)) return [u.declaration('u') + ';', 'unsigned long *up UNUSED = &u;', @@ -399,7 +398,7 @@ def int_cast_sequence(self): curr_expr = base for t in cast_sequence: curr_expr = ctree.mkCast(site, curr_expr, t) - self.assertEqual(cast_sequence[-1].cmp(curr_expr.ctype()), 0) + self.assertTrue(cast_sequence[-1].eq(curr_expr.ctype())) self.assertEqual(curr_expr.expr, base) @subtest() @@ -417,7 +416,7 @@ def int_cast_sequence_bit_increase(self): def cast_to_int(self, old_type, old_value, new_type, new_value): cast = ctree.mkCast(site, ctree.mkLit(site, old_value, old_type), new_type) - self.assertTrue(types.compatible_types(cast.ctype(), new_type)) + self.assertTrue(cast.ctype().eq(new_type)) return ['EXPECT(%s == %s);' % (cast.read(), new_value)] def unendianed_type(self, endian_type): @@ -460,8 +459,8 @@ def as_int(self, expr, value): ]) def endian_int_cast(self, expr, endiantype): """Check that casting to endianint results in the right type""" - self.assertEqual( - ctree.mkCast(site, expr, endiantype).ctype().cmp(endiantype), 0) + self.assertTrue( + ctree.mkCast(site, expr, endiantype).ctype().eq(endiantype)) @subtest([(targettype, endiantype) for targettype in ( @@ -483,7 +482,7 @@ def endian_int_cast_from(self, targettype, endiantype): ctree.mkIntegerConstant(site, -5, True), endiantype) cast = ctree.mkCast(site, expr, targettype) - self.assertEqual(cast.ctype().cmp(targettype), 0) + self.assertTrue(cast.ctype().eq(targettype)) return ['%s UNUSED = %s;' % (targettype.declaration('x'), cast.read())] @subtest([(op, lh, rh) @@ -535,7 +534,7 @@ def as_int_binop_coverage(self, op, lh, rh): types.TInt(64, True))]) def as_int_ifexpr(self, lh, rh, expected_type): expr = ctree.mkIfExpr(site, variable('b', types.TBool()), lh, rh) - self.assertEqual(expr.type.cmp(expected_type), 0) + self.assertTrue(expr.type.eq(expected_type)) return ["bool b = false;", "EXPECT(%s == %s);" % ( expr.read(), ctree.mkCast(site, rh, expected_type).read())] @@ -572,7 +571,7 @@ def as_int_unop_coverage(self, op, lh): def as_int_new_coverage(self, count): """Checks for as_int coverage""" expr = ctree.mkNew(site, types.TInt(8, False), count) - self.assertEqual(expr.type.cmp(types.TPtr(types.TInt(8, False))), 0) + self.assertTrue(expr.type.eq(types.TPtr(types.TInt(8, False)))) return ["EXPECT(%s == 3);" % expr.count.read()] def expect_int_unop(self, const, unop, expected): @@ -1025,8 +1024,7 @@ def add_sub_ptr(self, ptype): sub1 = ctree.mkSubtract(site, ptr, ctree.mkUnaryMinus(site, i)) sub2 = ctree.mkSubtract(site, ptr, add1) for expr in (add1, add2, sub1): - self.assertTrue(types.compatible_types( - expr.ctype().base, ptype)) + self.assertTrue(expr.ctype().base.eq(ptype)) self.expect_int_type(sub2.ctype(), True) stmts.extend([ 'EXPECT(%s == exp);' % (add1.read(),), diff --git a/py/dml/structure.py b/py/dml/structure.py index be33f3099..1e938cd5b 100644 --- a/py/dml/structure.py +++ b/py/dml/structure.py @@ -737,7 +737,13 @@ def typecheck_method_override(m1, m2, location): # TODO move to caller (_, type1) = eval_type(t1, a1.site, location, global_scope) (_, type2) = eval_type(t2, a2.site, location, global_scope) - if safe_realtype(type1).cmp(safe_realtype(type2)) != 0: + type1 = safe_realtype_unconst(type1) + type2 = safe_realtype_unconst(type2) + + ok = (type1.eq_fuzzy(type2) + if compat.lenient_typechecking in dml.globals.enabled_compat + else type1.eq(type2)) + if not ok: raise EMETH(a1.site, a2.site, f"mismatching types in input argument {n1}") @@ -746,7 +752,12 @@ def typecheck_method_override(m1, m2, location): ((n1, t1), (n2, t2)) = (a1.args, a2.args) (_, type1) = eval_type(t1, a1.site, location, global_scope) (_, type2) = eval_type(t2, a2.site, location, global_scope) - if safe_realtype(type1).cmp(safe_realtype(type2)) != 0: + type1 = safe_realtype_unconst(type1) + type2 = safe_realtype_unconst(type2) + ok = (type1.eq_fuzzy(type2) + if compat.lenient_typechecking in dml.globals.enabled_compat + else type1.eq(type2)) + if not ok: msg = "mismatching types in return value" if len(outp1) > 1: msg += f" {i + 1}" diff --git a/py/dml/traits.py b/py/dml/traits.py index 8f2fe73f7..f54b2253d 100644 --- a/py/dml/traits.py +++ b/py/dml/traits.py @@ -8,7 +8,7 @@ import contextlib import abc import os -from . import objects, logging, crep, codegen, toplevel, topsort +from . import objects, logging, crep, codegen, toplevel, topsort, compat from .logging import * from .codegen import * from .symtab import * @@ -401,11 +401,21 @@ def typecheck_method_override(left, right): if throws0 != throws1: raise EMETH(site0, site1, "different nothrow annotations") for ((n, t0), (_, t1)) in zip(inp0, inp1): - if realtype(t0).cmp(realtype(t1)) != 0: + t0 = safe_realtype_unconst(t0) + t1 = safe_realtype_unconst(t1) + ok = (t0.eq_fuzzy(t1) + if compat.lenient_typechecking in dml.globals.enabled_compat + else t0.eq(t1)) + if not ok: raise EMETH(site0, site1, "mismatching types in input argument %s" % (n,)) for (i, ((_, t0), (_, t1))) in enumerate(zip(outp0, outp1)): - if realtype(t0).cmp(realtype(t1)) != 0: + t0 = safe_realtype_unconst(t0) + t1 = safe_realtype_unconst(t1) + ok = (t0.eq_fuzzy(t1) + if compat.lenient_typechecking in dml.globals.enabled_compat + else t0.eq(t1)) + if not ok: raise EMETH(site0, site1, "mismatching types in output argument %d" % (i + 1,)) diff --git a/py/dml/types.py b/py/dml/types.py index 049331671..bfcb8996a 100644 --- a/py/dml/types.py +++ b/py/dml/types.py @@ -12,10 +12,11 @@ 'realtype', 'safe_realtype_shallow', 'safe_realtype', + 'safe_realtype_unconst', 'conv_const', + 'shallow_const', 'deep_const', 'type_union', - 'compatible_types', 'typedefs', 'global_type_declaration_order', 'global_anonymous_structs', @@ -60,6 +61,7 @@ from . import output import dml .globals import abc +import zlib class DMLTypeError(Exception): pass @@ -133,7 +135,7 @@ def realtype(t): elif isinstance(t, TVector): t2 = realtype(t.base) if t2 != t: - return TVector(t2, t.const) + return TVector(t2, t.const, t.uniq) elif isinstance(t, TFunction): input_types = tuple(realtype(sub) for sub in t.input_types) output_type = realtype(t.output_type) @@ -168,13 +170,34 @@ def conv_const(const, t): t.const = True return t +def safe_realtype_unconst(t0): + def sub(t): + if isinstance(t, TArray): + base = sub(t.base) + if t.const or base is not t.base: + t = t.clone() + t.const = False + t.base = base + elif t.const: + t = t.clone() + t.const = False + return t + return sub(safe_realtype(t0)) + +def shallow_const(t): + t = safe_realtype_shallow(t) + while not t.const and isinstance(t, TArray): + t = safe_realtype_shallow(t.base) + + return t.const + def deep_const(origt): subtypes = [origt] while subtypes: st = safe_realtype_shallow(subtypes.pop()) if st.const: return True - if isinstance(st, (TArray, TVector)): + if isinstance(st, TArray): subtypes.append(st.base) elif isinstance(st, StructType): subtypes.extend(st.members.values()) @@ -192,15 +215,15 @@ def __init__(self, types): def __eq__(self, other): if not isinstance(other, TypeSequence): - return NotImplemented + return False return (len(self.types) == len(other.types) - and all(typ_self.cmp(typ_other) == 0 + and all(typ_self.eq(typ_other) for (typ_self, typ_other) in zip(self.types, other.types))) def __hash__(self): - return hash(tuple(type(elem) for elem in self.types)) + return hash(tuple(elem.hashed() for elem in self.types)) class DMLType(metaclass=abc.ABCMeta): '''The type of a value (expression or declaration) in DML. One DML @@ -230,26 +253,54 @@ def sizeof(self): '''Return size, or None if not known''' return None - def cmp(self, other): - """Compare this type to another. - - Return 0 if the types are equivalent, - Return NotImplemented otherwise. + def eq(self, other : 'DMLType') -> bool: + """Strict type equivalence. + + Return True if the types are equivalent, and False otherwise + + "Type equivalence" has three minimal criteria: + 1. The C representations of the types MUST be compatible, in a C sense + 2. A value of one type can be treated as a value of the other type + without any additional risk of undefined behavior, invalid generated C, + failed invariants, or incorrect behavior whatsoever. In particular, + any valid value of one type can be treated as a valid value of the + other type. + 3. DMLC treats both types identically in the sense that substituting + one type with the other would not affect the validity and/or semantics + of any piece of model code. + + Conditions (1) and (2) together are called "type compatibility", and + parallels the C concept. + + For example, all trait reference types share the same C representation, + and so satisfy (1), but trait reference types for different traits do + not share vtables; trying to use a vtable for one trait with an + incompatible reference would result in undefined behavior, and so do + not satisfy (2). + Any bitfields type compared to the integer type it's based on + satisfy (1) and (2), but don't satisfy (3) as DMLC permits making + subreferences to the members of the bitfields for the bitfields type, + but does not permit doing so for the base integer type. + """ + return type(self) is type(other) and self.const == other.const - The exact meaning of this is somewhat fuzzy. The - method is used for three purposes: + def eq_fuzzy(self, other): + """Compare this type to another. + Return True if the types are pretty much equivalent, and False + otherwise - 1. in TPtr.canstore(), to judge whether pointer target types - are compatible. - 2. in ctree, to compare how large values different numerical - types can hold - 3. when judging whether a method override is allowed, as an inaccurate - replacement of TPtr(self).canstore(TPtr(other))[0] + As implied, the exact meaning of this is fuzzy. It relaxes criteria + (1) and (2) of 'eq', and does away with criteria (3) entirely. + For example, using eq_fuzzy to compare any bitfields with its base + integer type will return True, but also, + TPtr(void).eq_fuzzy(TPtr(TBool())) is allowed to return True, as is + TPtr(TBool()).eq_fuzzy(TArray(TBool())). - See SIMICS-9504 for further discussions. + Most notably, cmp_fuzzy does not take const-qualification into account. + Any usage of cmp_fuzzy should be considered a HACK """ - return NotImplemented + return safe_realtype_unconst(self).eq(safe_realtype_unconst(other)) def canstore(self, other): """Can a variable of this type store a value of another type. @@ -258,11 +309,9 @@ def canstore(self, other): boolean result. The 'trunc' is a (unused) flag that indicates a possible truncation, and the 'const' flag indicates that there would be a const violation. - - The correctness of the return value can not be trusted; see - SIMICS-9504 for further discussions. """ - return (self.cmp(other) == 0, False, False) + return (safe_realtype_unconst(self).eq(safe_realtype_unconst(other)), + False, False) @abc.abstractmethod def clone(self): @@ -279,7 +328,23 @@ def print_declaration(self, var, init = None, unused = False): def describe(self): raise Exception("%s.describe not implemented" % self.__class__.__name__) + def hashed(self): + '''Hash the DML type in a way compatible with eq. I.e. + a.eq(b) implies a.hashed() == b.hashed()''' + assert type(self).eq is DMLType.eq, \ + '.eq() overridden without overriding .hashed()' + return hash((type(self), self.const)) + def key(self): + '''A string key uniquely identifying the DML type, and is suitable for + use in checkpointing. Will raise DMLUnkeyableType if such a key can't + be produced. + + If neither a, b is unkeyable, then: + * a.eq(b) must imply a.key() == b.key() + * a.key() == b.key() implies a.eq(b) with overwhelming likelihood + (though not necessarily guaranteed; see IntegerType.key()) + ''' return self.const_str + self.describe() def describe_assign_types(self): @@ -299,8 +364,6 @@ def resolve(self): class TVoid(DMLType): __slots__ = () void = True - def __init__(self): - DMLType.__init__(self) def __repr__(self): return 'TVoid()' def describe(self): @@ -308,9 +371,7 @@ def describe(self): def declaration(self, var): return 'void ' + self.const_str + ' ' + var def clone(self): - return TVoid() - def cmp(self, other): - return 0 if isinstance(realtype(other), TVoid) else NotImplemented + return TVoid(self.const) class TUnknown(DMLType): '''A type unknown to DML. Typically used for a generic C macro @@ -339,13 +400,11 @@ def __init__(self, name, const=False): def __repr__(self): return 'TDevice(%s)' % repr(self.name) def c_name(self): - return f'{self.name} *{self.const_str}' + return f'{self.name} *' def describe(self): return 'pointer to %s' % self.name def key(self): return 'device' - def cmp(self, other): - return 0 if isinstance(realtype(other), TDevice) else NotImplemented def canstore(self, other): constviol = False if not self.const and other.const: @@ -356,7 +415,7 @@ def canstore(self, other): def clone(self): return TDevice(self.name, self.const) def declaration(self, var): - return f'{self.c_name()}{var}' + return f'{self.c_name()}{self.const_str}{var}' # Hack: some identifiers that are valid in DML are not allowed in C, # either because they are reserved words or because they clash with @@ -398,8 +457,12 @@ def describe(self): return self.c def key(self): raise ICE(self.declaration_site, 'need realtype before key') - def cmp(self, other): - assert False, 'need realtype before cmp' + def eq(self, other): + assert False, 'need realtype before eq' + def eq_fuzzy(self, other): + assert False, 'need realtype before eq_fuzzy' + def hashed(self): + assert False, 'need realtype before hashed' def clone(self): return TNamed(self.c, self.const) @@ -409,30 +472,23 @@ def declaration(self, var): class TBool(DMLType): __slots__ = () - def __init__(self): - DMLType.__init__(self) - def __repr__(self): return 'TBool(%r)' % self.const def describe(self): return 'bool' def declaration(self, var): return 'bool ' + self.const_str + var - def cmp(self, other): - if isinstance(other, TBool): - return 0 - return NotImplemented def canstore(self, other): constviol = False - if isinstance(other, TBool): + if type(other) is TBool: return (True, False, constviol) if (other.is_int and other.bits == 1 and not other.signed): return (True, False, constviol) return (False, False, constviol) def clone(self): - return TBool() + return TBool(self.const) class IntegerType(DMLType): '''Type that can contain an integer value @@ -441,7 +497,7 @@ class IntegerType(DMLType): members are bitfield accessors ''' __slots__ = ('bits', 'signed', 'members') - def __init__(self, bits, signed, members = None, const = False): + def __init__(self, bits, signed, members=None, const=False): DMLType.__init__(self, const) self.bits = bits assert isinstance(signed, bool) @@ -454,10 +510,34 @@ def __init__(self, bits, signed, members = None, const = False): is_int = True is_arith = True is_endian = False + @property def is_bitfields(self): return self.members is not None + def describe_backing_type(self): + raise Exception("%s.describe_backing_type not implemented" + % (self.__class__.__name__,)) + + def describe(self): + return (f'bitfields (backing type: {self.describe_backing_type()})' + if self.is_bitfields else self.describe_backing_type()) + + def key(self): + if not self.is_bitfields: + return DMLType.key(self) + + backing = self.describe_backing_type() + + # using adler32 as a quick, dirty, short, implementation-consistent + # hash that doesn't pull in any dependencies + # This *could* result in hash collisions, but they are unlikely to + # matter due to backing integer type being part of the key in any case + hsh = zlib.adler32(';'.join( + f'{name}:{t.key()}@{msb}-{lsb}' + for (name, (t, msb, lsb)) in self.members.items()).encode('utf-8')) + return f'{self.const_str}bitfields({backing}, {hsh})' + @property def bytes(self): return (self.bits - 1) // 8 + 1 @@ -476,29 +556,49 @@ def get_member_qualified(self, member): t = (conv_const(self.const, t[0]),) + t[1:] return t - def cmp(self, other): - if not other.is_int: - return NotImplemented - if self.is_endian: - if not other.is_endian: - return NotImplemented - if self.byte_order != other.byte_order: - return NotImplemented - elif other.is_endian: - return NotImplemented - if isinstance(self, TLong) != isinstance(other, TLong): - return NotImplemented - if isinstance(self, TSize) != isinstance(other, TSize): - return NotImplemented - if isinstance(self, TInt64_t) != isinstance(other, TInt64_t): - return NotImplemented + def eq(self, other): + if not DMLType.eq(self, other): + return False + if (self.bits, self.signed) != (other.bits, other.signed): + return False + if self.members is not other.members: + if self.members is None or other.members is None: + return False + + # Slow path, when the two bitfields types have different origins + + # The specifications have to be completely equivalent. + # We don't normalize reps via any sorting, as the order matters for + # compound initializers; we are not lenient on names, as that + # affects mkSubRef and designated initializers; we are not lenient + # on types because they SHOULD affect mkSubRef even though they + # don't today (see SIMICS-8857 and SIMICS-18394) + if (len(self.members) != len(other.members) + or any((name0, msb0, lsb0) != (name1, msb1, lsb1) + or not t0.eq(t1) + for ((name0, (t0, msb0, lsb0)), + (name1, (t1, msb1, lsb1))) + in zip(self.members.items(), other.members.items()))): + return False + + return True + + def eq_fuzzy(self, other): + if type(self) is not type(other): + return False if (dml.globals.dml_version == (1, 2) and compat.dml12_int in dml.globals.enabled_compat): # Ignore signedness - return 0 if self.bits == other.bits else NotImplemented + return self.bits == other.bits else: - return (0 if (self.bits, self.signed) == (other.bits, other.signed) - else NotImplemented) + return (self.bits, self.signed) == (other.bits, other.signed) + + def hashed(self): + members = (tuple((name, typ.hashed(), lsb, msb) + for (name, (typ, lsb, msb)) in self.members.items()) + if self.is_bitfields else None) + return hash((type(self), self.const, self.bits, self.signed, members)) + # This is the most restrictive canstore definition for # IntegerTypes, if this is overridden then it should be # because we want to be less restrictive @@ -523,16 +623,16 @@ class TInt(IntegerType): because it means that C's operational semantics is what's used in practice. ''' - __slots__ = () - def __init__(self, bits, signed, members = None, const = False): + __slots__ = ('label',) + def __init__(self, bits, signed, members=None, label=None, const=False): IntegerType.__init__(self, bits, signed, members, const) + self.label = label def describe(self): - s = 'int%d' % self.bits - if self.signed: - return s - else: - return 'u' + s + return self.label if self.label else IntegerType.describe(self) + + def describe_backing_type(self): + return f'{"u"*(not self.signed)}int{self.bits}' def __repr__(self): return 'TInt(%r,%r,%r,%r)' % (self.bits, self.signed, self.members, self.const) @@ -572,7 +672,8 @@ def canstore(self, other): return (False, False, constviol) def clone(self): - return TInt(self.bits, self.signed, self.members, self.const) + return TInt(self.bits, self.signed, self.members, self.label, + self.const) def declaration(self, var): t = self.apitype() @@ -590,9 +691,9 @@ def __init__(self, signed, const=False): const=const) def c_name(self): - return self.const_str + ('long' if self.signed else 'unsigned long') + return 'long' if self.signed else 'unsigned long' - def describe(self): + def describe_backing_type(self): return self.c_name() def __repr__(self): @@ -602,7 +703,7 @@ def clone(self): return TLong(self.signed, self.const) def declaration(self, var): - return f'{self.c_name()} {var}' + return f'{self.const_str}{self.c_name()} {var}' class TSize(IntegerType): '''The 'size_t' type from C''' @@ -611,9 +712,9 @@ def __init__(self, signed, const=False): IntegerType.__init__(self, 64, signed, const=const) def c_name(self): - return self.const_str + ('ssize_t' if self.signed else 'size_t') + return 'ssize_t' if self.signed else 'size_t' - def describe(self): + def describe_backing_type(self): return self.c_name() def __repr__(self): @@ -623,7 +724,7 @@ def clone(self): return TSize(self.signed, self.const) def declaration(self, var): - return f'{self.c_name()} {var}' + return f'{self.const_str}{self.c_name()} {var}' class TInt64_t(IntegerType): '''The '[u]int64_t' type from ISO C. For compatibility with C @@ -637,10 +738,9 @@ def __init__(self, signed, const=False): IntegerType.__init__(self, 64, signed, const=const) def c_name(self): - name = 'int64_t' if self.signed else 'uint64_t' - return f'const {name}' if self.const else name + return 'int64_t' if self.signed else 'uint64_t' - def describe(self): + def describe_backing_type(self): return self.c_name() def __repr__(self): @@ -650,7 +750,7 @@ def clone(self): return TInt64_t(self.signed, self.const) def declaration(self, var): - return f'{self.c_name()} {var}' + return f'{self.const_str}{self.c_name()} {var}' class TEndianInt(IntegerType): '''An integer where the byte storage order is defined. @@ -658,7 +758,7 @@ class TEndianInt(IntegerType): dmllib.h ''' __slots__ = ('byte_order') - def __init__(self, bits, signed, byte_order, members = None, const = False): + def __init__(self, bits, signed, byte_order, members=None, const=False): IntegerType.__init__(self, bits, signed, members, const) if (bits % 8 != 0): raise DMLTypeError("Trying to create endian int without whole " @@ -672,11 +772,11 @@ def big_endian(self): return self.byte_order == 'big-endian' def c_name(self): - return '%s%sint%d_%s_t' % ( - self.const_str, "" if self.signed else "u", self.bits, + return '%sint%d_%s_t' % ( + "" if self.signed else "u", self.bits, "be" if self.big_endian else "le") - def describe(self): + def describe_backing_type(self): return self.c_name() def __repr__(self): @@ -700,6 +800,17 @@ def dmllib_store(self): def dmllib_load(self): return self.dmllib_fun("load") + def eq(self, other): + return (IntegerType.eq(self, other) + and self.byte_order == other.byte_order) + + def eq_fuzzy(self, other): + return (IntegerType.eq_fuzzy(self, other) + and self.byte_order == other.byte_order) + + def hashed(self): + return hash((IntegerType.hashed(self), self.byte_order)) + def get_store_fun(self): """function reference to dmllib function used to store values to an endianint""" @@ -717,7 +828,7 @@ def clone(self): self.byte_order, self.members, self.const) def declaration(self, var): - return f'{self.c_name()} {var}' + return f'{self.const_str}{self.c_name()} {var}' class TFloat(DMLType): __slots__ = ('name',) @@ -730,10 +841,12 @@ def __repr__(self): return '%s(%r,%r)' % (self.__class__.__name__, self.name, self.const) def describe(self): return self.name - def cmp(self, other): - if other.is_float and self.name == other.name: - return 0 - return NotImplemented + def eq(self, other): + return (self.const == other.const + and other.is_float and self.name == other.name) + + def hashed(self): + return hash((TFloat, self.const, self.name)) def canstore(self, other): constviol = False @@ -765,8 +878,10 @@ def key(self): % (conv_const(self.const, self.base).key(), self.size.value)) def describe(self): - return 'array of size %s of %s' % (self.size.read(), - self.base.describe()) + const = self.const or self.base.const + return 'array of size %s of %s%s' % (str(self.size), + 'const '*const, + self.base.describe()) def declaration(self, var): return self.base.declaration(self.const_str + var + '[' + self.size.read() + ']') @@ -784,16 +899,28 @@ def sizeof(self): if elt_size == None: return None return self.size.value * elt_size - def cmp(self, other): - if compat.dml12_misc in dml.globals.enabled_compat: - if isinstance(other, (TArray, TPtr)): - return self.base.cmp(other.base) - elif isinstance(other, (TPtr, TArray)): - if self.base.void or other.base.void: - return 0 - if self.base.cmp(other.base) == 0: - return 0 - return NotImplemented + + def eq(self, other): + if not isinstance(other, TArray): + return False + if not (self.size is other.size + or (self.size.constant and other.size.constant + and self.size.value == other.size.value)): + return False + return conv_const(self.const, self.base).eq( + conv_const(other.const, other.base)) + + def eq_fuzzy(self, other): + if isinstance(other, (TArray, TPtr)): + return other.base.void or self.base.eq_fuzzy(other.base) + return False + + def hashed(self): + size = self.size.value if self.size.constant else self.size + return hash((TArray, + size, + conv_const(self.const, self.base).hashed())) + def canstore(self, other): return (False, False, False) def clone(self): @@ -814,35 +941,47 @@ def __repr__(self): def key(self): return f'{self.const_str}pointer({self.base.key()})' def describe(self): - return 'pointer to %s' % (self.base.describe()) - def cmp(self, other): - if compat.dml12_misc in dml.globals.enabled_compat: - if isinstance(other, TPtr): - # Can only compare for voidness or equality - if self.base.void or other.base.void: - return 0 - if self.base.cmp(other.base) == 0: - return 0 - elif isinstance(other, (TPtr, TArray)): + return 'pointer to %s%s' % (self.base.const_str, + self.base.describe()) + def eq(self, other): + return DMLType.eq(self, other) and self.base.eq(other.base) + + def eq_fuzzy(self, other): + if isinstance(other, (TPtr, TArray)): if self.base.void or other.base.void: - return 0 - if self.base.cmp(other.base) == 0: - return 0 - return NotImplemented + return True + return self.base.eq_fuzzy(other.base) + return False + + def hashed(self): + return hash((TPtr, self.const, self.base.hashed())) def canstore(self, other): ok = False trunc = False constviol = False if isinstance(other, (TPtr, TArray)): + constviol = (not shallow_const(self.base) + and shallow_const(other.base)) if self.base.void or other.base.void: ok = True + if compat.lenient_typechecking in dml.globals.enabled_compat: + constviol = False else: - if not self.base.const and other.base.const: - constviol = True - ok = (self.base.cmp(other.base) == 0) + unconst_self_base = safe_realtype_unconst(self.base) + unconst_other_base = safe_realtype_unconst(other.base) + + ok = (unconst_self_base.eq_fuzzy + if (compat.dml12_int in dml.globals.enabled_compat + and unconst_self_base.is_int + and unconst_other_base.is_int) + else unconst_self_base.eq)(unconst_other_base) + elif isinstance(other, TFunction): - ok = True + ok = (compat.lenient_typechecking in dml.globals.enabled_compat + or safe_realtype_unconst(self.base).eq(other)) + # TODO gate this behind dml.globals.dml_version == (1, 2) or + # dml12_misc? if self.base.void and isinstance(other, TDevice): ok = True #dbg('TPtr.canstore %r %r => %r' % (self, other, ok)) @@ -862,28 +1001,36 @@ def resolve(self): return self class TVector(DMLType): - __slots__ = ('base',) - def __init__(self, base, const = False): + count = 0 + __slots__ = ('base', 'uniq',) + def __init__(self, base, const=False, uniq=None): DMLType.__init__(self, const) + if uniq is None: + uniq = TVector.count + TVector.count += 1 + self.uniq = uniq if not base: raise DMLTypeError("Null base") self.base = base def __repr__(self): return "TVector(%r,%r)" % (self.base, self.const) def key(self): - return f'{self.const_str}vector({self.base.key()})' + raise DMLUnkeyableType(self) def describe(self): return 'vector of %s' % self.base.describe() - def cmp(self, other): + def eq(self, other): + return DMLType.eq(self, other) and self.uniq == other.uniq + def eq_fuzzy(self, other): if isinstance(other, TVector): # Can only compare for voidness or equality if self.base.void or other.base.void: - return 0 - if self.base.cmp(other.base) == 0: - return 0 - return NotImplemented + return True + return self.base.eq_fuzzy(other.base) + return False + def hashed(self): + return hash((TVector, self.const, self.uniq)) def clone(self): - return TVector(self.base, self.const) + return TVector(self.base, self.const, self.uniq) def declaration(self, var): s = self.base.declaration('') return 'VECT(%s) %s%s' % (s, self.const_str, var) @@ -901,25 +1048,25 @@ def __repr__(self): return "TTrait(%s)" % (self.trait.name,) def clone(self): - return TTrait(self.trait) + return TTrait(self.trait, self.const) - def cmp(self, other): - if isinstance(other, TTrait) and self.trait is other.trait: - return 0 - else: - return NotImplemented + def eq(self, other): + return DMLType.eq(self, other) and self.trait is other.trait def key(self): return f'{self.const_str}trait({self.trait.name})' + def hashed(self): + return hash((TTrait, self.const, self.trait)) + def c_name(self): - return f'{self.const_str}{cident(self.trait.name)}' + return cident(self.trait.name) def describe(self): - return 'trait ' + self.trait.name + return 'template type ' + self.trait.name def declaration(self, var): - return f'{self.c_name()} {var}' + return f'{self.const_str}{self.c_name()} {var}' class TTraitList(DMLType): __slots__ = ('traitname') @@ -934,15 +1081,15 @@ def __repr__(self): def clone(self): return TTraitList(self.traitname, self.const) - def cmp(self, other): - if isinstance(other, TTraitList) and self.traitname == other.traitname: - return 0 - else: - return NotImplemented + def eq(self, other): + return DMLType.eq(self, other) and self.traitname == other.traitname def key(self): return f'{self.const_str}sequence({self.traitname})' + def hashed(self): + return hash((TTraitList, self.const, self.traitname)) + def c_type(self): return f'{self.const_str}_each_in_t' @@ -1010,10 +1157,11 @@ def declaration(self, var): raise EANONEXT(self.declaration_site) return "%s %s%s" % (self.typename, self.const_str, var) - def cmp(self, other): - if isinstance(other, TExternStruct) and self.id == other.id: - return 0 - return NotImplemented + def eq(self, other): + return DMLType.eq(self, other) and self.id == other.id + + def hashed(self): + return hash((TExternStruct, self.const, self.id)) def clone(self): return TExternStruct(self.members, self.id, self.typename, self.const) @@ -1065,10 +1213,11 @@ def print_struct_definition(self): output.site_linemark(self.declaration_site) out("};\n", preindent = -1) - def cmp(self, other): - if isinstance(other, TStruct) and self.label == other.label: - return 0 - return NotImplemented + def eq(self, other): + return DMLType.eq(self, other) and self.label == other.label + + def hashed(self): + return hash((TStruct, self.const, self.label)) def clone(self): return TStruct(self.members, self.label, self.const) @@ -1190,9 +1339,10 @@ def const(self, value): def key(self): return ('%sfunction(%s%s)->(%s)' % (self.const_str, - ','.join(t.key() for t in self.input_types), + ','.join(safe_realtype_unconst(t).key() + for t in self.input_types), ',...' * self.varargs, - self.output_type.key())) + safe_realtype_unconst(self.output_type).key())) def describe(self): inparams = ",".join([t.describe() if t else "?" @@ -1202,16 +1352,32 @@ def describe(self): return ('function(%s) returning %s' % (inparams, self.output_type.describe())) - def cmp(self, other): - if (isinstance(other, TFunction) - and len(self.input_types) == len(other.input_types) - and all(arg1.cmp(arg2) == 0 - for (arg1, arg2) in zip(self.input_types, - other.input_types)) - and self.output_type.cmp(other.output_type) == 0 - and self.varargs == other.varargs): - return 0 - return NotImplemented + def eq(self, other): + return (isinstance(other, TFunction) + and len(self.input_types) == len(other.input_types) + and all( + safe_realtype_unconst(arg1).eq(safe_realtype_unconst(arg2)) + for (arg1, arg2) + in zip(self.input_types, other.input_types)) + and safe_realtype_unconst(self.output_type).eq( + safe_realtype_unconst(other.output_type)) + and self.varargs == other.varargs) + + def eq_fuzzy(self, other): + return (isinstance(other, TFunction) + and len(self.input_types) == len(other.input_types) + and all(arg1.eq_fuzzy(arg2) + for (arg1, arg2) + in zip(self.input_types, other.input_types)) + and self.output_type.eq_fuzzy(other.output_type) + and self.varargs == other.varargs) + + def hashed(self): + return hash((TFunction, + tuple(safe_realtype_unconst(typ).hashed() + for typ in self.input_types), + safe_realtype_unconst(self.output_type).hashed(), + self.varargs)) def canstore(self, other): return (False, False, False) @@ -1241,15 +1407,17 @@ def __repr__(self): def clone(self): return THook(self.msg_types, self.validated, self.const) - def cmp(self, other): - if (isinstance(other, THook) - and len(self.msg_types) == len(other.msg_types) - and all(own_comp.cmp(other_comp) == 0 - for (own_comp, other_comp) in zip(self.msg_types, - other.msg_types))): - return 0 - else: - return NotImplemented + def eq(self, other): + return (DMLType.eq(self, other) + and len(self.msg_types) == len(other.msg_types) + and all(own_comp.eq(other_comp) + for (own_comp, other_comp) in zip(self.msg_types, + other.msg_types))) + + def hashed(self): + return hash((THook, + self.const, + tuple(comp.hashed() for comp in self.msg_types))) def key(self): return ('%shook(%s)' @@ -1308,14 +1476,6 @@ def type_union(type1, type2): return type1 return type2 -def compatible_types(type1, type2): - # This function intends to verify that two DML types are - # compatible in the sense defined by the C spec, possibly with - # some DML-specific restrictions added. TODO: DMLType.cmp is only - # a rough approximation of this; we should write tests and - # either repair cmp or rewrite the logic from scratch. - return type1.cmp(type2) == 0 - void = TVoid() # These are the named types used. This includes both "imported" # typedefs for types declared in C header files, and types defined in diff --git a/py/dml/types_test.py b/py/dml/types_test.py new file mode 100644 index 000000000..c8dc1f6e3 --- /dev/null +++ b/py/dml/types_test.py @@ -0,0 +1,284 @@ +# © 2024 Intel Corporation +# SPDX-License-Identifier: MPL-2.0 + +from dml.types import * +import dml.globals +from dml import ctree +from dml import expr +from dml import traits +from dml import logging +from contextlib import contextmanager + +import unittest + +class TestClone(unittest.TestCase): + def test(self): + # types which support clone + typ0 = TVoid() + for typ in (TVoid(), + TNamed("int"), + TBool(), + TInt(8, False, {}), + TEndianInt(8, False, 'big-endian', {}), + TFloat("a"), + TArray(typ0, ctree.mkIntegerLiteral(0, 2)), + TPtr(typ0), + TVector(typ0), + TTrait(object()), + TStruct({"name": TInt(32, False)}), + TLayout("big-endian", {}), + TDevice("a")): + typ_clone = typ.clone() + self.assertTrue( + realtype(typ_clone).eq(realtype(typ))) + self.assertTrue( + realtype(typ).eq(realtype(typ_clone))) + typ_clone.const = True + self.assertFalse(typ.const) + typ = typ_clone.clone() + self.assertTrue(typ.const) + + # special case for TraitList, because realtype requires global + # state (typedefs) + typ = TTraitList("a") + typ_clone = typ.clone() + self.assertTrue(typ.eq(typ_clone)) + self.assertTrue(typ_clone.eq(typ)) + dml.globals.dml_version = (1, 2) + # types which do not support clone + with self.assertRaises(logging.ICE): + TUnknown().clone() + +class Typ1(DMLType): + def describe(self): + return 'Typ1' + def clone(self): + return Typ1(self.const) + +class Typ2(DMLType): + def describe(self): + return 'Typ2' + def clone(self): + return Typ1(self.const) + +class TestEq(unittest.TestCase): + def assert_eq(self, t1, t2): + self.assertTrue(t1.eq(t2)) + self.assertTrue(t2.eq(t1)) + self.assertEqual(t1.hashed(), t2.hashed()) + try: + self.assertEqual(t1.key(), t2.key()) + except DMLUnkeyableType: + pass + + def assert_neq(self, t1, t2, hash_collision=False): + self.assertFalse(t1.eq(t2)) + self.assertFalse(t2.eq(t1)) + if not hash_collision: + self.assertNotEqual(t1.hashed(), t2.hashed()) + try: + self.assertNotEqual(t1.key(), t2.key()) + except DMLUnkeyableType: + pass + + def test_DMLType(self): + self.assert_eq(Typ1(), Typ1()) + self.assert_neq(Typ1(True), Typ1()) + self.assert_neq(Typ1(), Typ2()) + + def test_IntegerType(self): + self.assert_eq(TInt(8, False), TInt(8, False)) + self.assert_neq(TInt(16, False), TInt(8, False)) + # Not equivalent even though the C representations of uint16 and uint13 + # are compatible + self.assert_neq(TInt(16, False), TInt(13, False)) + # Signedness + self.assert_neq(TInt(8, True), TInt(8, False)) + + # bitfields + self.assert_neq(TInt(8, False), TInt(8, False, members={})) + + def bitfields(): + return TInt(32, False, { 'a': (TInt(8, False), 13, 6), + 'b': (TInt(13, False), 27, 15) }) + + self.assert_eq(TInt(8, False, members={}), TInt(8, False, members={})) + self.assert_eq(bitfields(), bitfields()) + + @contextmanager + def neq_bitfields_testcase(): + t = bitfields() + yield t + self.assert_neq(bitfields(), t) + + # Presence of members matter + with neq_bitfields_testcase() as t: + del t.members['a'] + + # type of members matters + with neq_bitfields_testcase() as t: + t.members['a'][0].signed = True + + # msb/lsb of members matters + with neq_bitfields_testcase() as t: + (mt, msb, lsb) = t.members['a'] + t.members['a'] = (mt, msb - 1, lsb - 1) + + # order matters + with neq_bitfields_testcase() as t: + t.members = dict(reversed(t.members.items())) + + # names matter + with neq_bitfields_testcase() as t: + t.members['c'] = t.members['b'] + del t.members['b'] + + def test_TEndianInt(self): + self.assert_eq(TEndianInt(8, False, 'big-endian'), + TEndianInt(8, False, 'big-endian')) + self.assert_neq(TEndianInt(8, False, 'big-endian'), + TEndianInt(8, False, 'little-endian')) + + def test_TFloat(self): + self.assert_eq(TFloat('double'), TFloat('double')) + self.assert_neq(TFloat('double'), TFloat('float')) + + def mkTArray(self, size, signed=False): + size = (ctree.mkIntegerConstant(None, size, signed) if size is not None + else expr.mkLit(None, 'lit', TInt(32, False))) + return TArray(TInt(32, False), size) + + def test_TArray(self): + # Arrays of constant size need the sizes be equal to be equal. + # The type of the size expression is irrelevant. + self.assert_eq(self.mkTArray(4), self.mkTArray(4)) + self.assert_eq(self.mkTArray(4), self.mkTArray(4, True)) + self.assert_neq(self.mkTArray(4), self.mkTArray(5)) + + # Arrays of non-constant size needs the underlying expression to be + # identical in order to be equal. + self.assert_neq(self.mkTArray(None), self.mkTArray(None)) + a = self.mkTArray(None) + b = self.mkTArray(None) + b.size = a.size + self.assert_eq(a, b) + + @contextmanager + def array_testcase(validate): + a = self.mkTArray(4) + b = self.mkTArray(4) + yield (a, b) + validate(a, b) + + def eq_array_testcase(): return array_testcase(self.assert_eq) + def neq_array_testcase(): return array_testcase(self.assert_neq) + + # Base type matters + with neq_array_testcase() as (a, _): + a.base.signed = True + + # Constness: constness of base type and array are normalized + with neq_array_testcase() as (a, _): + a.const = True + with eq_array_testcase() as (a, b): + a.const = True + b.const = True + with eq_array_testcase() as (a, b): + a.base.const = True + b.const = True + with eq_array_testcase() as (a, b): + a.base = self.mkTArray(4) + b.base = self.mkTArray(4) + a.base.base.const = True + b.const = True + + # Pointers are not equivalent to arrays + self.assert_neq(self.mkTArray(4), TPtr(TInt(32, False))) + + def test_TPtr(self): + self.assert_eq(TPtr(Typ1()), TPtr(Typ1())) + self.assert_neq(TPtr(Typ1()), TPtr(Typ2())) + self.assert_neq(TPtr(Typ1(), const=True), TPtr(Typ1())) + self.assert_neq(TPtr(Typ1(const=True)), TPtr(Typ1())) + self.assert_neq(TPtr(Typ1(), const=True), + TPtr(Typ1(const=True))) + + # void pointers are not special + self.assert_neq(TPtr(Typ1()), TPtr(TVoid())) + + def test_TVector(self): + v1 = TVector(Typ1()) + self.assert_eq(v1, v1.clone()) + v2 = v1.clone() + v2.const = True + self.assert_neq(v1, v2) + self.assert_neq(v1, TVector(Typ1())) + + def test_TTrait(self): + tr1 = traits.Trait(None, 't1', set(), {}, {}, {}, {}, {}, {}, {}) + tr2 = traits.Trait(None, 't2', set(), {}, {}, {}, {}, {}, {}, {}) + + self.assert_eq(TTrait(tr1), TTrait(tr1)) + self.assert_neq(TTrait(tr1), TTrait(tr2)) + + def test_TTraitList(self): + self.assert_eq(TTraitList('t1'), TTraitList('t1')) + self.assert_neq(TTraitList('t1'), TTraitList('t2')) + + + def test_TStruct(self): + members = {'a': Typ1()} + t1 = TStruct(members, label="a_struct") + t2 = TStruct(members) + + self.assert_eq(t1, TStruct(members, label="a_struct")) + self.assert_neq(t1, t2) + self.assert_eq(t2, t2.clone()) + self.assert_neq(t2, TStruct(members)) + + def test_TExternStruct(self): + self.assert_eq(TExternStruct({}, 0), TExternStruct({}, 0)) + self.assert_eq(TExternStruct({}, "str"), TExternStruct({}, "str")) + self.assert_neq(TExternStruct({}, 0), TExternStruct({}, "str")) + + def test_TFunction(self): + self.assert_eq(TFunction((), TVoid()), TFunction((), TVoid())) + self.assert_neq(TFunction((Typ1(),), TVoid()), TFunction((), TVoid())) + + self.assert_eq(TFunction((Typ1(), TPtr(TVoid())), TPtr(Typ2())), + TFunction((Typ1(), TPtr(TVoid())), TPtr(Typ2()))) + self.assert_neq(TFunction((Typ1(), TPtr(TVoid())), TPtr(Typ1())), + TFunction((Typ1(), TPtr(TVoid())), TPtr(Typ2()))) + self.assert_neq(TFunction((Typ2(), TPtr(TVoid())), TPtr(Typ2())), + TFunction((Typ1(), TPtr(TVoid())), TPtr(Typ2()))) + self.assert_neq(TFunction((TPtr(TVoid()), Typ1()), TPtr(Typ2())), + TFunction((Typ1(), TPtr(TVoid())), TPtr(Typ2()))) + + # Direct constness doesn't matter + self.assert_eq(TFunction((Typ1(True), TPtr(TVoid(), True)), + TPtr(Typ2(), True)), + TFunction((Typ1(), TPtr(TVoid())), TPtr(Typ2()))) + + # Constness behind indirection does + self.assert_neq(TFunction((Typ1(), TPtr(TVoid(True))), TPtr(Typ2())), + TFunction((Typ1(), TPtr(TVoid())), TPtr(Typ2()))) + self.assert_neq(TFunction((Typ1(), TPtr(TVoid())), TPtr(Typ2(True))), + TFunction((Typ1(), TPtr(TVoid())), TPtr(Typ2()))) + + # Variadicity matters + self.assert_eq(TFunction((), TVoid(), varargs=True), + TFunction((), TVoid(), varargs=True)) + self.assert_neq(TFunction((), TVoid()), + TFunction((), TVoid(), varargs=True)) + + def test_THook(self): + self.assert_eq(THook(()), THook(())) + self.assert_neq(THook(()), THook((Typ1(),))) + + self.assert_eq(THook((Typ1(), Typ2())), THook((Typ1(), Typ2()))) + + # Order matters + self.assert_neq(THook((Typ1(), Typ2())), THook((Typ2(), Typ1()))) + + # Direct constness matters (for now; might change with tuple types) + self.assert_neq(THook((Typ1(True), Typ2())), THook((Typ1(), Typ2()))) diff --git a/test/1.4/errors/T_ECAST.dml b/test/1.4/errors/T_ECAST.dml index 9736eda8e..15eb6f762 100644 --- a/test/1.4/errors/T_ECAST.dml +++ b/test/1.4/errors/T_ECAST.dml @@ -10,6 +10,8 @@ typedef struct { uint32 x; } s_t; typedef layout "little-endian" { uint32 x; } l_t; /// WARNING WEXPERIMENTAL typedef int vect v_t; +/// WARNING WEXPERIMENTAL +typedef int vect alt_v_t; typedef int a_t[1]; typedef void f_t(void); extern f_t f; @@ -43,9 +45,11 @@ method init() { cast(v, uint32); /// ERROR ECAST cast(i, v_t); - /// ERROR ECAST + // no error cast(v, v_t); /// ERROR ECAST + cast(v, alt_v_t); + /// ERROR ECAST cast(l, uint32); // no error! cast(a, uint32); diff --git a/test/1.4/hooks/T_basic.dml b/test/1.4/hooks/T_basic.dml index 00848437c..a5de03219 100644 --- a/test/1.4/hooks/T_basic.dml +++ b/test/1.4/hooks/T_basic.dml @@ -36,15 +36,15 @@ template hookset_test is hookset { assert h0.suspended == 0 && resumed == 2 && count == 2 && storage == 7; count = storage = 0; - local int i = 5; + local bf_t i = 5; after h1 -> p: one_param(p); assert h1.suspended == 1 && i == 5; resumed = h1.send_now(&i); assert h1.suspended == 0 && resumed == 1 && i == 6; - after h1 -> p: one_serializable_param(-5); + after h1 -> p: one_serializable_param(11); assert h1.suspended == 1 && storage == 0 && i == 6; resumed = h1.send_now(&i); - assert h1.suspended == 0 && resumed == 1 && storage == -5 && i == 6; + assert h1.suspended == 0 && resumed == 1 && storage == 11 && i == 6; storage = i = 0; after h2 -> (p, i): two_params(p, i); @@ -85,11 +85,11 @@ template hookset_test is hookset { resumed = h1.send_now(&i); assert h1.suspended == 0 && resumed == 1 && i == 3 * 7 + 5; i = 0; - after h1 -> p: indexed[3][5].one_serializable_param(-5); + after h1 -> p: indexed[3][5].one_serializable_param(11); assert h1.suspended == 1 && storage_indexed == 0 && i == 0; resumed = h1.send_now(&i); assert h1.suspended == 0 && resumed == 1 - && storage_indexed == -5 * (3 * 7 + 5) && i == 0; + && storage_indexed == 11 * (3 * 7 + 5) && i == 0; storage_indexed = 0; after h2 -> (p, i): indexed[3][5].two_params(p, i); diff --git a/test/1.4/hooks/T_checkpointing.dml b/test/1.4/hooks/T_checkpointing.dml index d35580a47..1893269c0 100644 --- a/test/1.4/hooks/T_checkpointing.dml +++ b/test/1.4/hooks/T_checkpointing.dml @@ -63,7 +63,7 @@ attribute test_state is write_only_attr { count = storage = storage_indexed = 0; last_i_indexed = last_j_indexed = -1; - local int x = 4; + local bf_t x = 4; resumed = obj.h1.send_now(&x); assert resumed == 2 && x == 5 && storage == 9; storage = 0; diff --git a/test/1.4/hooks/common.dml b/test/1.4/hooks/common.dml index 1fc99237b..315480d59 100644 --- a/test/1.4/hooks/common.dml +++ b/test/1.4/hooks/common.dml @@ -4,48 +4,50 @@ */ dml 1.4; -session int count; +typedef bitfields 16 { uint6 a @[12:7]; } bf_t; + +session bf_t count; method no_params() { ++count; } -method one_param(int *x) { +method one_param(bf_t *x) { ++*x; } -method two_params(int *x, int i) { +method two_params(bf_t *x, bf_t i) { *x = i; } -session int storage; -method one_serializable_param(int i) { +session bf_t storage; +method one_serializable_param(bf_t i) { storage = i; } -session int storage_indexed; +session bf_t storage_indexed; session (int last_i_indexed, int last_j_indexed) = (-1, -1); group indexed[i < 5][j < 7] { method no_params() { (last_i_indexed, last_j_indexed) = (i, j); } - method one_param(int *x) { + method one_param(bf_t *x) { *x = i*7 + j; } - method two_params(int *x, int coeff) { + method two_params(bf_t *x, bf_t coeff) { *x = coeff * (i*7 + j); } - method one_serializable_param(int coeff) { + method one_serializable_param(bf_t coeff) { storage_indexed = coeff * (i*7 + j); } } template hookset { hook() _h0; - hook(int *) _h1; - hook(int *, int) _h2; + hook(bf_t *) _h1; + hook(bf_t *, bf_t) _h2; hook() _h3[6][8]; - hook(int *) _h4[6][8]; - hook(int *, int) _h5[6][8]; + hook(bf_t *) _h4[6][8]; + hook(bf_t *, bf_t) _h5[6][8]; - hook(int) _h6; + hook(bf_t) _h6; param h0 default _h0; param h1 default _h1; @@ -73,15 +75,15 @@ method enforce_h0_ref(hook() h) -> (hook()) { return h; } -method enforce_h1_ref(hook(int *) h) -> (hook(int *)) { +method enforce_h1_ref(hook(bf_t *) h) -> (hook(bf_t *)) { return h; } -method enforce_h2_ref(hook(int *, int) h) -> (hook(int *, int)) { +method enforce_h2_ref(hook(bf_t *, bf_t) h) -> (hook(bf_t *, bf_t)) { return h; } -method enforce_h6_ref(hook(int) h) -> (hook(int)) { +method enforce_h6_ref(hook(bf_t) h) -> (hook(bf_t)) { return h; } @@ -89,8 +91,8 @@ group via_hookref is hookset_set { in each hookset { is init; session hook() h3_arr[6][8]; - session hook(int *) h4_arr[6][8]; - session hook(int *, int) h5_arr[6][8]; + session hook(bf_t *) h4_arr[6][8]; + session hook(bf_t *, bf_t) h5_arr[6][8]; method init() { for (local int idx_0 = 0; idx_0 < 6; ++idx_0) { for (local int idx_1 = 0; idx_1 < 8; ++idx_1) { diff --git a/test/1.4/legacy/T_lenient_typechecking_disabled.dml b/test/1.4/legacy/T_lenient_typechecking_disabled.dml new file mode 100644 index 000000000..f8313057b --- /dev/null +++ b/test/1.4/legacy/T_lenient_typechecking_disabled.dml @@ -0,0 +1,12 @@ +/* + © 2024 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; + +device test; + +/// COMPILE-ONLY +/// DMLC-FLAG --no-compat=lenient_typechecking +/// SCAN-FOR-TAGS lenient_typechecking.dml +import "lenient_typechecking.dml"; diff --git a/test/1.4/legacy/T_lenient_typechecking_enabled.dml b/test/1.4/legacy/T_lenient_typechecking_enabled.dml new file mode 100644 index 000000000..df34070f2 --- /dev/null +++ b/test/1.4/legacy/T_lenient_typechecking_enabled.dml @@ -0,0 +1,11 @@ +/* + © 2024 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; + +device test; + +/// COMPILE-ONLY +/// DMLC-FLAG --simics-api=7 +import "lenient_typechecking.dml"; diff --git a/test/1.4/legacy/lenient_typechecking.dml b/test/1.4/legacy/lenient_typechecking.dml new file mode 100644 index 000000000..1775a54f2 --- /dev/null +++ b/test/1.4/legacy/lenient_typechecking.dml @@ -0,0 +1,44 @@ +/* + © 2024 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ + +dml 1.4; + +// expectations in this file are selectively enabled using SCAN-FOR-TAGS + +template t { + shared method sm(const int *p) -> (const int *) default { return p; } + shared method m(const int *p) -> (const int *); + method n(const int *p) -> (const int *) default { return p; } +} + +template u is t { + /// ERROR EMETH + shared method sm(int *p) -> (int *) { return p; } +} + +group g is u { + /// ERROR EMETH + method m(int *p) -> (int *) { return p; } + + /// ERROR EMETH + method n(int *p) -> (int *) { return p; } +} + +header %{ + #define MACRO(a,b,c) ((void)0) +%} + +extern void MACRO(void *a, int *b, void (*c)(void)); + +method init() { + // no error + MACRO(NULL, NULL, NULL); + /// ERROR ECONSTP + MACRO(cast(NULL, const int *), NULL, NULL); + /// ERROR ECONSTP + MACRO(NULL, cast(NULL, const void *), NULL); + /// ERROR EPTYPE + MACRO(NULL, NULL, MACRO); +}