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);
+}