Compile Time Changes

if-as statement and expression cleanup

In 13.5, the if-as statement and if-as expression were quite "magical", able to apply a wide variety of conversions including function calls and constructors. This can create confusing and error-prone situations.

In 14.0, the if-as statement is limited to membership testing and unboxing conversions (see below). The if-as expression is now exactly equivalent in behaviour to the if-as statement.

Example A

public void foo(Object o) {
    // These two are equivalent
    if (o as Str) {pln(o.v);}
    if (o as str) {pln(o);}

    // These two are equivalent
    if (o as Int) {pln(o.v);}
    if (o as int) {pln(o);}   // new in 14.0

    // These two are equivalent in 14.0
    if (o as int) {pln(o);} else {pln("not an int");}
    pln(o as int ? o : "not an int");
}

Example B

public class A {
    public constructor() {pln(this, " is created");}
}

public class B {
    public constructor(A a) {pln(this, " is created from A");}
}

public class C extends A {
    public constructor() {}
    public constructor(A a) {pln(this, " is created from A");}
}

public void foo(A a) {
    pln("a=", a);
    if (a as B) {pln("a as B=", a);} // forbidden in 14.0
    if (a as C) {pln("a as C=", a);}
}
{
    foo(A());
    pln();
    foo(C());
}

Since B does not inherit from A, if (a as B) is not valid in 14.0. It only works in 13.5 because there is a constructor which "converts" A into a new instance of B.

This happened in practice with DialogWindow and Button, respectively, since Button has a constructor with one required argument of type Window (and DialogWindow is a Window).

Output in 13.5

A(175) is created
a=A(175)
B(165) is created from A
a as B=B(165)

C(171) is created
a=C(171)
B(161) is created from A
a as B=B(161)
a as C=C(171)

Output in 14.0

A(36) is created
a=A(36)

C(79) is created
a=C(79)
a as C=C(79)

Dynamic compilation improvements (cm.runtime.compileFun)

The compileFun CM function has been replaced with a native implementation, which is faster and cleaner. The remove optional argument has been removed as the new version avoids writing the code to disk in any case.

Old: public Function compileFun(str def, bool output=false, bool remove=true)
New: public Function compileFun(str def, bool output=false)

Old: public Function compileFun(StrBuf def, bool output=false, bool remove=true)
New: public Function compileFun(StrBuf def, bool output=false)

A similar function which returns all compiled functions in the file, rather than just the first one, has been added.

Added: public Function[] compileFuns(str def, bool output=false)
Added: public Function[] compileFuns(StrBuf def, bool output=false)

Type conversion search correctness and optimization

The internal code responsible for finding the appropriate conversion path between types (if any) has been significantly improved. There is a small risk of surprising behaviour since the new algorithm performs an exhaustive search and selects the "best" path, rather than using flawed pruning heuristics.

In some circumstances, the type conversion search may recurse forever; the old version would treat this as a failure to find an appropriate conversion, the new version will treat this as an error.

Save/load robustness improvements

Old: public bool cm.io.easySave(Object obj, Url url, bool pkgVersions=true, bool fast=false);
New: public bool cm.io.easySave(Object obj, Url url, bool pkgVersions=true, bool fast=false, bool verifyCRC=false);

Old: public bool cm.io.object.safeReplaceUrl(Url this, Url target, bool xtrace=false)
New: public bool cm.io.object.safeReplaceUrl(Url this, Url target, bool verifyCRC=false, bool xtrace=false)

Implicit-this for functions taking unqualified value types

public void foo(FooClass this) {
    pln(field); // equivalent to this.field
}
public void foo(fooValue& this) {
    pln(field); // equivalent to this.field
}
public void foo(fooValue this) {
    pln(field); // equivalent to this.field in 14.0
}

Garbage collector interface

The garbage collector (GC) has two different pause-states; "frozen" and "parked". The former prevents almost all GC activity, the latter forces the GC to finish any pending work and prevents all GC activity. The GC interface now exposes gcUnPark so that CM code can enter and leave the "parked" state.

Added: gcUnPark();

DLL interface changes

14.0 makes many changes to the CM-DLL interface, you must rebuild all DLLs.

The CM-DLL interface now uses a secondary version check, allowing breaking API changes without necessarily changing the size of the DllStartInfo struct.

Due to internal refactoring, funFunLinkOffset has been removed; use getFunLink(Fun*) instead.

Removed: int funFunLinkOffset;

Default C++ compilation flags changes

We have enabled MSVC errors for comparisons between signed and unsigned values. If you encounter code which you cannot migrate for this (e.g. third-party libraries), add the following lines to your DLL's Makefile to disable the errors.

CFLAGS := $(filter-out /w34287,$(CFLAGS))
CFLAGS := $(filter-out /w34388,$(CFLAGS))

Runtime/Behavior Changes

Daylight-savings (DST) handling

Time conversion functions throughout various parts of the system were incorrectly handling daylight-savings transitions. This has been fixed.

Object::setField now follows the type rules

The setField method used to permit almost any value type so long as the type of the destination field was assignable to Object. This is a severe violation of the type system's rules and thus in the new version, setField ensures that the type of the instance passed in is assignable to the type of the destination field.

Basically, the same rules that apply to normal field assignment (instance.field = value) now apply to setField (instance.setField("field", value)). There is an exception for unboxing -- for example, you can use setField to set an int field, even though you're passing in an Int, since setField takes an Object.

Example

public class Foo {
    public constructor() {}
    public str->str map_field;
}

{
    Foo f();
    f.setField("map_field", new str->str());    // ok
    f.setField("map_field", new str->Object()); // forbidden in 14.0
}

Changes to severe internal error handling (bail out)

When CM has a severe internal error, it will perform a "bail out". In some circumstances, the bail out may itself cause further errors. In 14.0, many of those situations are now handled by "fast fail" instead. If minidump generation or JIT debugging are configured, they will be invoked, otherwise the application will terminate ungracefully.

Some extremely severe errors (e.g. garbage collector state corruption) may go directly to "fast fail" without a "bail out" attempt, leaving no trace in log files.

Miscellaneous

Fixed File::getStrLine bugs

This function is no longer prone to infinite loops and properly generates a string of characters, not their ASCII codes.

Logging behaviour improvements

The internal printf wrapper function, which is also used for pln and pnn, should now handle invalid characters by replacing them rather than silently aborting output (and skipping the logfile).

Memory usage analysis tools (cm.runtime.memUsage/"Measure memory usage")

The MemUsage tool has been refactored for better performance and significantly lower memory usage.

Fixed name collision issues between non-interactive and interactive run blocks

Executing code using C-M-SPC (cm-compilation-interact-line) in a file with top-level run blocks used to cause problems due to using the same namespace. This is fixed in 14.0.