Skip to content

Commit 74e0178

Browse files
authored
Fix missing reference conversion case (#1058)
1 parent 7b7e13b commit 74e0178

14 files changed

+6177
-111
lines changed

src/builtins.ts

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2170,7 +2170,7 @@ export function compileCall(
21702170
}
21712171

21722172
// otherwise call abort if the assertion is false-ish
2173-
let abort = compileAbort(compiler, operands.length == 2 ? operands[1] : null, reportNode);
2173+
let abort = compiler.makeAbort(operands.length == 2 ? operands[1] : null, reportNode);
21742174
compiler.currentType = type.nonNullableType;
21752175
if (contextualType == Type.void) { // simplify if dropped anyway
21762176
compiler.currentType = Type.void;
@@ -4726,49 +4726,6 @@ function deferASM(
47264726
);
47274727
}
47284728

4729-
/** Compiles an abort wired to the conditionally imported 'abort' function. */
4730-
export function compileAbort(
4731-
compiler: Compiler,
4732-
message: Expression | null,
4733-
reportNode: Node
4734-
): ExpressionRef {
4735-
var program = compiler.program;
4736-
var module = compiler.module;
4737-
4738-
var stringInstance = compiler.program.stringInstance;
4739-
if (!stringInstance) return module.unreachable();
4740-
4741-
var abortInstance = program.abortInstance;
4742-
if (!(abortInstance && compiler.compileFunction(abortInstance))) return module.unreachable();
4743-
4744-
var messageArg: ExpressionRef;
4745-
if (message !== null) {
4746-
// The message argument works much like an arm of an IF that does not become executed if the
4747-
// assertion succeeds respectively is only being computed if the program actually crashes.
4748-
// Hence, let's make it so that the autorelease is skipped at the end of the current block,
4749-
// essentially ignoring the message GC-wise. Doesn't matter anyway on a crash.
4750-
messageArg = compiler.compileExpression(message, stringInstance.type, Constraints.CONV_IMPLICIT | Constraints.WILL_RETAIN);
4751-
} else {
4752-
messageArg = compiler.makeZero(stringInstance.type);
4753-
}
4754-
4755-
var filenameArg = compiler.ensureStaticString(reportNode.range.source.normalizedPath);
4756-
4757-
compiler.currentType = Type.void;
4758-
return module.block(null, [
4759-
module.call(
4760-
abortInstance.internalName, [
4761-
messageArg,
4762-
filenameArg,
4763-
module.i32(reportNode.range.line),
4764-
module.i32(reportNode.range.column)
4765-
],
4766-
NativeType.None
4767-
),
4768-
module.unreachable()
4769-
]);
4770-
}
4771-
47724729
/** Compiles the `visit_globals` function. */
47734730
export function compileVisitGlobals(compiler: Compiler): void {
47744731
var module = compiler.module;

src/compiler.ts

Lines changed: 136 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import {
77
BuiltinNames,
88
compileCall as compileBuiltinCall,
9-
compileAbort,
109
compileVisitGlobals,
1110
compileVisitMembers,
1211
compileRTTI,
@@ -2499,7 +2498,9 @@ export class Compiler extends DiagnosticEmitter {
24992498
let newArgs = (<NewExpression>value).arguments;
25002499
if (newArgs.length) message = newArgs[0]; // FIXME: naively assumes type string
25012500
}
2502-
stmts.push(compileAbort(this, message, statement));
2501+
stmts.push(
2502+
this.makeAbort(message, statement)
2503+
);
25032504

25042505
return this.module.flatten(stmts);
25052506
}
@@ -3125,21 +3126,48 @@ export class Compiler extends DiagnosticEmitter {
31253126
// any to void
31263127
if (toType.kind == TypeKind.VOID) return module.drop(expr);
31273128

3128-
if (this.currentFlow.isNonnull(expr, fromType)) fromType = fromType.nonNullableType;
3129+
// reference involved
3130+
if (fromType.is(TypeFlags.REFERENCE) || toType.is(TypeFlags.REFERENCE)) {
3131+
if (this.currentFlow.isNonnull(expr, fromType)) {
3132+
fromType = fromType.nonNullableType;
3133+
} else if (explicit && fromType.is(TypeFlags.NULLABLE) && !toType.is(TypeFlags.NULLABLE)) {
3134+
// explicit conversion from nullable to non-nullable requires a runtime
3135+
// check here because nonnull state above already didn't know better
3136+
if (!this.options.noAssert) {
3137+
expr = this.makeRuntimeNonNullCheck(expr, fromType, reportNode);
3138+
}
3139+
fromType = fromType.nonNullableType;
3140+
}
3141+
if (fromType.isAssignableTo(toType)) { // downcast or same
3142+
assert(fromType.kind == toType.kind);
3143+
this.currentType = toType;
3144+
return expr;
3145+
}
3146+
if (explicit && toType.nonNullableType.isAssignableTo(fromType)) { // upcast
3147+
// <Cat | null>(<Animal>maybeCat)
3148+
assert(fromType.kind == toType.kind);
3149+
if (!this.options.noAssert) {
3150+
expr = this.makeRuntimeUpcastCheck(expr, fromType, toType, reportNode);
3151+
}
3152+
this.currentType = toType;
3153+
return expr;
3154+
}
3155+
this.error(
3156+
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
3157+
reportNode.range, fromType.toString(), toType.toString()
3158+
);
3159+
this.currentType = toType;
3160+
return module.unreachable();
3161+
}
3162+
3163+
// not dealing with references from here on
31293164

31303165
if (!fromType.isAssignableTo(toType)) {
31313166
if (!explicit) {
3132-
if (fromType.nonNullableType == toType) {
3133-
this.error(
3134-
DiagnosticCode.Object_is_possibly_null,
3135-
reportNode.range
3136-
); // recoverable
3137-
} else {
3138-
this.error(
3139-
DiagnosticCode.Conversion_from_type_0_to_1_requires_an_explicit_cast,
3140-
reportNode.range, fromType.toString(), toType.toString()
3141-
); // recoverable
3142-
}
3167+
this.error(
3168+
DiagnosticCode.Conversion_from_type_0_to_1_requires_an_explicit_cast,
3169+
reportNode.range, fromType.toString(), toType.toString()
3170+
); // recoverable
31433171
}
31443172
}
31453173

@@ -3319,19 +3347,9 @@ export class Compiler extends DiagnosticEmitter {
33193347
expression.expression.range
33203348
);
33213349
} else if (!this.options.noAssert) {
3322-
let module = this.module;
3323-
let flow = this.currentFlow;
3324-
let temp = flow.getTempLocal(type);
3325-
if (!flow.canOverflow(expr, type)) flow.setLocalFlag(temp.index, LocalFlags.WRAPPED);
3326-
flow.setLocalFlag(temp.index, LocalFlags.NONNULL);
3327-
expr = module.if(
3328-
module.local_tee(temp.index, expr),
3329-
module.local_get(temp.index, type.toNativeType()),
3330-
module.unreachable()
3331-
);
3332-
flow.freeTempLocal(temp);
3350+
expr = this.makeRuntimeNonNullCheck(expr, type, expression);
33333351
}
3334-
this.currentType = this.currentType.nonNullableType;
3352+
this.currentType = type.nonNullableType;
33353353
return expr;
33363354
}
33373355
default: assert(false);
@@ -9376,6 +9394,98 @@ export class Compiler extends DiagnosticEmitter {
93769394
}
93779395
return stmts;
93789396
}
9397+
9398+
/** Makes a call to `abort`, if present, otherwise creates a trap. */
9399+
makeAbort(
9400+
/** Message argument of type string, if any. */
9401+
message: Expression | null,
9402+
/** Code location to report when aborting. */
9403+
codeLocation: Node
9404+
): ExpressionRef {
9405+
var program = this.program;
9406+
var module = this.module;
9407+
var stringInstance = program.stringInstance;
9408+
var abortInstance = program.abortInstance;
9409+
if (!abortInstance || !this.compileFunction(abortInstance)) return module.unreachable();
9410+
9411+
var messageArg: ExpressionRef;
9412+
if (message !== null) {
9413+
// The message argument works much like an arm of an IF that does not become executed if the
9414+
// assertion succeeds respectively is only being computed if the program actually crashes.
9415+
// Hence, let's make it so that the autorelease is skipped at the end of the current block,
9416+
// essentially ignoring the message GC-wise. Doesn't matter anyway on a crash.
9417+
messageArg = this.compileExpression(message, stringInstance.type, Constraints.CONV_IMPLICIT | Constraints.WILL_RETAIN);
9418+
} else {
9419+
messageArg = this.makeZero(stringInstance.type);
9420+
}
9421+
9422+
var filenameArg = this.ensureStaticString(codeLocation.range.source.normalizedPath);
9423+
return module.block(null, [
9424+
module.call(
9425+
abortInstance.internalName, [
9426+
messageArg,
9427+
filenameArg,
9428+
module.i32(codeLocation.range.line),
9429+
module.i32(codeLocation.range.column)
9430+
],
9431+
NativeType.None
9432+
),
9433+
module.unreachable()
9434+
]);
9435+
}
9436+
9437+
/** Makes a runtime non-null check, e.g. on `<Type>possiblyNull` or `possiblyNull!`. */
9438+
makeRuntimeNonNullCheck(
9439+
/** Expression being checked. */
9440+
expr: ExpressionRef,
9441+
/** Type of the expression. */
9442+
type: Type,
9443+
/** Report node. */
9444+
reportNode: Node
9445+
): ExpressionRef {
9446+
assert(type.is(TypeFlags.NULLABLE | TypeFlags.REFERENCE));
9447+
var module = this.module;
9448+
var flow = this.currentFlow;
9449+
var temp = flow.getTempLocal(type);
9450+
if (!flow.canOverflow(expr, type)) flow.setLocalFlag(temp.index, LocalFlags.WRAPPED);
9451+
flow.setLocalFlag(temp.index, LocalFlags.NONNULL);
9452+
expr = module.if(
9453+
module.local_tee(temp.index, expr),
9454+
module.local_get(temp.index, type.toNativeType()),
9455+
this.makeAbort(null, reportNode) // TODO: throw
9456+
);
9457+
flow.freeTempLocal(temp);
9458+
return expr;
9459+
}
9460+
9461+
/** Makes a runtime upcast check, e.g. on `<Child>parent`. */
9462+
makeRuntimeUpcastCheck(
9463+
/** Expression being upcast. */
9464+
expr: ExpressionRef,
9465+
/** Type of the expression. */
9466+
type: Type,
9467+
/** Type casting to. */
9468+
toType: Type,
9469+
/** Report node. */
9470+
reportNode: Node
9471+
): ExpressionRef {
9472+
assert(toType.is(TypeFlags.REFERENCE) && toType.nonNullableType.isAssignableTo(type));
9473+
var module = this.module;
9474+
var flow = this.currentFlow;
9475+
var temp = flow.getTempLocal(type);
9476+
var instanceofInstance = this.program.instanceofInstance;
9477+
assert(this.compileFunction(instanceofInstance));
9478+
expr = module.if(
9479+
module.call(instanceofInstance.internalName, [
9480+
module.local_tee(temp.index, expr),
9481+
module.i32(assert(toType.classReference).id)
9482+
], NativeType.I32),
9483+
module.local_get(temp.index, type.toNativeType()),
9484+
this.makeAbort(null, reportNode) // TODO: throw
9485+
);
9486+
flow.freeTempLocal(temp);
9487+
return expr;
9488+
}
93799489
}
93809490

93819491
// helpers

std/assembly/rt/tlsf.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,12 +498,12 @@ export function allocateBlock(root: Root, size: usize): Block {
498498
block = searchBlock(root, payloadSize);
499499
if (!block) {
500500
growMemory(root, payloadSize);
501-
block = <Block>searchBlock(root, payloadSize);
501+
block = changetype<Block>(searchBlock(root, payloadSize));
502502
if (DEBUG) assert(block); // must be found now
503503
}
504504
} else {
505505
growMemory(root, payloadSize);
506-
block = <Block>searchBlock(root, payloadSize);
506+
block = changetype<Block>(searchBlock(root, payloadSize));
507507
if (DEBUG) assert(block); // must be found now
508508
}
509509
}

0 commit comments

Comments
 (0)