mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-19 18:01:31 +00:00
use gc interface directly, document
This commit is contained in:
271
src/builtins.ts
271
src/builtins.ts
@ -4079,277 +4079,6 @@ export function compileIterateRoots(compiler: Compiler): void {
|
||||
);
|
||||
}
|
||||
|
||||
export function compileBuiltinArrayGet(
|
||||
compiler: Compiler,
|
||||
target: Class,
|
||||
thisExpression: Expression,
|
||||
elementExpression: Expression,
|
||||
contextualType: Type
|
||||
): ExpressionRef {
|
||||
var type = assert(compiler.program.determineBuiltinArrayType(target));
|
||||
var module = compiler.module;
|
||||
var outType = (
|
||||
type.is(TypeFlags.INTEGER) &&
|
||||
contextualType.is(TypeFlags.INTEGER) &&
|
||||
contextualType.size > type.size
|
||||
) ? contextualType : type;
|
||||
|
||||
var dataStart = assert(target.lookupInSelf("dataStart"));
|
||||
assert(dataStart.kind == ElementKind.FIELD);
|
||||
var dataLength = assert(target.lookupInSelf("dataLength"));
|
||||
assert(dataLength.kind == ElementKind.FIELD);
|
||||
|
||||
// compile the index expression and shift it to become the actual byteOffset
|
||||
var dynamicOffset = compiler.compileExpression(
|
||||
elementExpression,
|
||||
Type.i32,
|
||||
ConversionKind.IMPLICIT,
|
||||
WrapMode.NONE
|
||||
);
|
||||
var alignLog2 = type.alignLog2;
|
||||
if (alignLog2) {
|
||||
dynamicOffset = module.createBinary(BinaryOp.ShlI32,
|
||||
dynamicOffset,
|
||||
module.createI32(alignLog2)
|
||||
);
|
||||
}
|
||||
|
||||
var usizeType = compiler.options.usizeType;
|
||||
var nativeSizeType = compiler.options.nativeSizeType;
|
||||
var ptrExpr: ExpressionRef;
|
||||
var constantOffset: i32 = 0;
|
||||
|
||||
// precompute byteOffset into a constant and a dynamic part
|
||||
dynamicOffset = module.precomputeExpression(dynamicOffset);
|
||||
if (getExpressionId(dynamicOffset) == ExpressionId.Const) {
|
||||
constantOffset = getConstValueI32(dynamicOffset);
|
||||
dynamicOffset = 0;
|
||||
} else if (getExpressionId(dynamicOffset) == ExpressionId.Binary) {
|
||||
if (getBinaryOp(dynamicOffset) == BinaryOp.AddI32) {
|
||||
let left = getBinaryLeft(dynamicOffset);
|
||||
let right = getBinaryRight(dynamicOffset);
|
||||
if (getExpressionId(left) == ExpressionId.Const) {
|
||||
constantOffset = getConstValueI32(left);
|
||||
dynamicOffset = right;
|
||||
} else if (getExpressionId(right) == ExpressionId.Const) {
|
||||
constantOffset = getConstValueI32(right);
|
||||
dynamicOffset = left;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ptr = this.dataStart
|
||||
ptrExpr = module.createLoad(usizeType.byteSize, true,
|
||||
compiler.compileExpression(
|
||||
thisExpression,
|
||||
target.type,
|
||||
ConversionKind.IMPLICIT,
|
||||
WrapMode.NONE
|
||||
),
|
||||
nativeSizeType, (<Field>dataStart).memoryOffset
|
||||
);
|
||||
// ptr = ptr + <usize>dynamicOffset
|
||||
if (dynamicOffset) {
|
||||
if (nativeSizeType == NativeType.I64) {
|
||||
ptrExpr = module.createBinary(BinaryOp.AddI64,
|
||||
ptrExpr,
|
||||
module.createUnary(UnaryOp.ExtendU32, dynamicOffset)
|
||||
);
|
||||
} else {
|
||||
ptrExpr = module.createBinary(BinaryOp.AddI32,
|
||||
ptrExpr,
|
||||
dynamicOffset
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
compiler.currentType = outType;
|
||||
return module.createLoad(
|
||||
type.byteSize,
|
||||
type.is(TypeFlags.SIGNED),
|
||||
ptrExpr,
|
||||
outType.toNativeType(),
|
||||
constantOffset
|
||||
);
|
||||
}
|
||||
|
||||
export function compileBuiltinArraySet(
|
||||
compiler: Compiler,
|
||||
target: Class,
|
||||
thisExpression: Expression,
|
||||
elementExpression: Expression,
|
||||
valueExpression: Expression,
|
||||
contextualType: Type
|
||||
): ExpressionRef {
|
||||
var type = assert(compiler.program.determineBuiltinArrayType(target));
|
||||
return compileBuiltinArraySetWithValue(
|
||||
compiler,
|
||||
target,
|
||||
thisExpression,
|
||||
elementExpression,
|
||||
compiler.compileExpression(
|
||||
valueExpression,
|
||||
type.is(TypeFlags.INTEGER | TypeFlags.VALUE)
|
||||
? type.is(TypeFlags.LONG)
|
||||
? type.is(TypeFlags.SIGNED)
|
||||
? Type.i64
|
||||
: Type.u64
|
||||
: type.is(TypeFlags.SIGNED)
|
||||
? Type.i32
|
||||
: Type.u32
|
||||
: type,
|
||||
ConversionKind.IMPLICIT,
|
||||
WrapMode.NONE
|
||||
),
|
||||
contextualType != Type.void
|
||||
);
|
||||
}
|
||||
|
||||
export function compileBuiltinArraySetWithValue(
|
||||
compiler: Compiler,
|
||||
target: Class,
|
||||
thisExpression: Expression,
|
||||
elementExpression: Expression,
|
||||
valueExpr: ExpressionRef,
|
||||
tee: bool
|
||||
): ExpressionRef {
|
||||
var type = assert(compiler.program.determineBuiltinArrayType(target));
|
||||
var module = compiler.module;
|
||||
|
||||
var dataStart = assert(target.lookupInSelf("dataStart"));
|
||||
assert(dataStart.kind == ElementKind.FIELD);
|
||||
var dataLength = assert(target.lookupInSelf("dataLength"));
|
||||
assert(dataLength.kind == ElementKind.FIELD);
|
||||
|
||||
var constantOffset: i32 = 0;
|
||||
var dynamicOffset = module.precomputeExpression(
|
||||
compiler.compileExpression(
|
||||
elementExpression,
|
||||
Type.i32,
|
||||
ConversionKind.IMPLICIT,
|
||||
WrapMode.NONE
|
||||
)
|
||||
);
|
||||
if (getExpressionId(dynamicOffset) == ExpressionId.Const) {
|
||||
constantOffset = getConstValueI32(dynamicOffset);
|
||||
dynamicOffset = 0;
|
||||
} else if (getExpressionId(dynamicOffset) == ExpressionId.Binary) {
|
||||
if (getBinaryOp(dynamicOffset) == BinaryOp.AddI32) {
|
||||
let left = getBinaryLeft(dynamicOffset);
|
||||
let right = getBinaryRight(dynamicOffset);
|
||||
if (getExpressionId(left) == ExpressionId.Const) {
|
||||
constantOffset = getConstValueI32(left);
|
||||
dynamicOffset = right;
|
||||
} else if (getExpressionId(right) == ExpressionId.Const) {
|
||||
constantOffset = getConstValueI32(right);
|
||||
dynamicOffset = left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var program = compiler.program;
|
||||
var isManaged = type.isManaged(program) && target.type.isManaged(program);
|
||||
var usizeType = compiler.options.usizeType;
|
||||
var nativeSizeType = compiler.options.nativeSizeType;
|
||||
var thisExpr = compiler.compileExpression(
|
||||
thisExpression,
|
||||
target.type,
|
||||
ConversionKind.IMPLICIT,
|
||||
WrapMode.NONE
|
||||
);
|
||||
var tempThis: Local | null = null;
|
||||
if (isManaged) {
|
||||
tempThis = compiler.currentFlow.getTempLocal(target.type, false);
|
||||
thisExpr = module.createTeeLocal(tempThis.index, thisExpr);
|
||||
}
|
||||
var dataStartExpr = module.createLoad(usizeType.byteSize, true,
|
||||
thisExpr, nativeSizeType, (<Field>dataStart).memoryOffset
|
||||
);
|
||||
|
||||
var typeAlignLog2 = type.alignLog2;
|
||||
constantOffset <<= typeAlignLog2;
|
||||
if (dynamicOffset) {
|
||||
if (typeAlignLog2) {
|
||||
dynamicOffset = module.createBinary(BinaryOp.ShlI32,
|
||||
dynamicOffset,
|
||||
module.createI32(typeAlignLog2)
|
||||
);
|
||||
}
|
||||
if (nativeSizeType == NativeType.I64) {
|
||||
dataStartExpr = module.createBinary(BinaryOp.AddI64,
|
||||
dataStartExpr,
|
||||
module.createUnary(UnaryOp.ExtendU32, dynamicOffset)
|
||||
);
|
||||
} else {
|
||||
dataStartExpr = module.createBinary(BinaryOp.AddI32,
|
||||
dataStartExpr,
|
||||
dynamicOffset
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// handle Array<Ref>: value = RETAIN<T, TArray>(value, this)
|
||||
if (isManaged) {
|
||||
let program = compiler.program;
|
||||
let retainInstance = compiler.resolver.resolveFunction(assert(program.retainPrototype), [ type, target.type ]);
|
||||
if (!retainInstance) return module.createUnreachable();
|
||||
valueExpr = compiler.makeCallInlinePrechecked(retainInstance, [
|
||||
valueExpr,
|
||||
module.createGetLocal(assert(tempThis).index, nativeSizeType)
|
||||
], 0, true);
|
||||
|
||||
// handle Uint8ClampedArray: value = ~(value >> 31) & (((255 - value) >> 31) | value)
|
||||
} else if (target.internalName == BuiltinSymbols.Uint8ClampedArray) {
|
||||
let tempLocal = compiler.currentFlow.getAndFreeTempLocal(Type.i32, true);
|
||||
valueExpr = module.createBinary(BinaryOp.AndI32,
|
||||
module.createBinary(BinaryOp.XorI32,
|
||||
module.createBinary(BinaryOp.ShrI32,
|
||||
module.createTeeLocal(tempLocal.index, valueExpr),
|
||||
module.createI32(31)
|
||||
),
|
||||
module.createI32(-1)
|
||||
),
|
||||
module.createBinary(BinaryOp.OrI32,
|
||||
module.createBinary(BinaryOp.ShrI32,
|
||||
module.createBinary(BinaryOp.SubI32,
|
||||
module.createI32(255),
|
||||
module.createGetLocal(tempLocal.index, NativeType.I32)
|
||||
),
|
||||
module.createI32(31)
|
||||
),
|
||||
module.createGetLocal(tempLocal.index, NativeType.I32)
|
||||
)
|
||||
);
|
||||
}
|
||||
assert(!tempThis);
|
||||
|
||||
var nativeType = type.toNativeType();
|
||||
|
||||
if (!tee) {
|
||||
compiler.currentType = Type.void;
|
||||
return module.createStore(
|
||||
type.byteSize,
|
||||
dataStartExpr,
|
||||
valueExpr,
|
||||
nativeType,
|
||||
constantOffset
|
||||
);
|
||||
} else {
|
||||
let flow = compiler.currentFlow;
|
||||
let tempLocal = flow.getAndFreeTempLocal(type, false);
|
||||
compiler.currentType = type;
|
||||
return module.createBlock(null, [
|
||||
module.createStore(
|
||||
type.byteSize,
|
||||
dataStartExpr,
|
||||
module.createTeeLocal(tempLocal.index, valueExpr),
|
||||
nativeType,
|
||||
constantOffset
|
||||
),
|
||||
module.createGetLocal(tempLocal.index, nativeType)
|
||||
], nativeType);
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensures that the specified class's GC hook exists and returns its function table index. */
|
||||
export function ensureGCHook(
|
||||
compiler: Compiler,
|
||||
|
281
src/codegen/array.ts
Normal file
281
src/codegen/array.ts
Normal file
@ -0,0 +1,281 @@
|
||||
// TBD: managed reference handling makes this cumbersome, and there is a binaryen pass that can
|
||||
// help propagating constant offsets. ideally, using operator overloads would be enough because
|
||||
// it's the most flexible way to do this.
|
||||
|
||||
// import { Compiler, ConversionKind, WrapMode } from "../compiler";
|
||||
// import { Class, ElementKind, Field, Local } from "../program";
|
||||
// import { Expression } from "../ast";
|
||||
// import { Type, TypeFlags } from "../types";
|
||||
// import { ExpressionRef, getExpressionId, getBinaryOp, getBinaryLeft, getBinaryRight, getConstValueI32, ExpressionId, BinaryOp, NativeType, UnaryOp } from "../module";
|
||||
// import { BuiltinSymbols } from "../builtins";
|
||||
|
||||
// export function makeArrayGet(
|
||||
// compiler: Compiler,
|
||||
// target: Class,
|
||||
// thisExpression: Expression,
|
||||
// elementExpression: Expression,
|
||||
// contextualType: Type
|
||||
// ): ExpressionRef {
|
||||
// var type = assert(compiler.program.determineBuiltinArrayType(target));
|
||||
// var module = compiler.module;
|
||||
// var outType = (
|
||||
// type.is(TypeFlags.INTEGER) &&
|
||||
// contextualType.is(TypeFlags.INTEGER) &&
|
||||
// contextualType.size > type.size
|
||||
// ) ? contextualType : type;
|
||||
|
||||
// var dataStart = assert(target.lookupInSelf("dataStart"));
|
||||
// assert(dataStart.kind == ElementKind.FIELD);
|
||||
// var dataLength = assert(target.lookupInSelf("dataLength"));
|
||||
// assert(dataLength.kind == ElementKind.FIELD);
|
||||
|
||||
// // compile the index expression and shift it to become the actual byteOffset
|
||||
// var dynamicOffset = compiler.compileExpression(
|
||||
// elementExpression,
|
||||
// Type.i32,
|
||||
// ConversionKind.IMPLICIT,
|
||||
// WrapMode.NONE
|
||||
// );
|
||||
// var alignLog2 = type.alignLog2;
|
||||
// if (alignLog2) {
|
||||
// dynamicOffset = module.createBinary(BinaryOp.ShlI32,
|
||||
// dynamicOffset,
|
||||
// module.createI32(alignLog2)
|
||||
// );
|
||||
// }
|
||||
|
||||
// var usizeType = compiler.options.usizeType;
|
||||
// var nativeSizeType = compiler.options.nativeSizeType;
|
||||
// var ptrExpr: ExpressionRef;
|
||||
// var constantOffset: i32 = 0;
|
||||
|
||||
// // precompute byteOffset into a constant and a dynamic part
|
||||
// dynamicOffset = module.precomputeExpression(dynamicOffset);
|
||||
// if (getExpressionId(dynamicOffset) == ExpressionId.Const) {
|
||||
// constantOffset = getConstValueI32(dynamicOffset);
|
||||
// dynamicOffset = 0;
|
||||
// } else if (getExpressionId(dynamicOffset) == ExpressionId.Binary) {
|
||||
// if (getBinaryOp(dynamicOffset) == BinaryOp.AddI32) {
|
||||
// let left = getBinaryLeft(dynamicOffset);
|
||||
// let right = getBinaryRight(dynamicOffset);
|
||||
// if (getExpressionId(left) == ExpressionId.Const) {
|
||||
// constantOffset = getConstValueI32(left);
|
||||
// dynamicOffset = right;
|
||||
// } else if (getExpressionId(right) == ExpressionId.Const) {
|
||||
// constantOffset = getConstValueI32(right);
|
||||
// dynamicOffset = left;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // ptr = this.dataStart
|
||||
// ptrExpr = module.createLoad(usizeType.byteSize, true,
|
||||
// compiler.compileExpression(
|
||||
// thisExpression,
|
||||
// target.type,
|
||||
// ConversionKind.IMPLICIT,
|
||||
// WrapMode.NONE
|
||||
// ),
|
||||
// nativeSizeType, (<Field>dataStart).memoryOffset
|
||||
// );
|
||||
// // ptr = ptr + <usize>dynamicOffset
|
||||
// if (dynamicOffset) {
|
||||
// if (nativeSizeType == NativeType.I64) {
|
||||
// ptrExpr = module.createBinary(BinaryOp.AddI64,
|
||||
// ptrExpr,
|
||||
// module.createUnary(UnaryOp.ExtendU32, dynamicOffset)
|
||||
// );
|
||||
// } else {
|
||||
// ptrExpr = module.createBinary(BinaryOp.AddI32,
|
||||
// ptrExpr,
|
||||
// dynamicOffset
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// compiler.currentType = outType;
|
||||
// return module.createLoad(
|
||||
// type.byteSize,
|
||||
// type.is(TypeFlags.SIGNED),
|
||||
// ptrExpr,
|
||||
// outType.toNativeType(),
|
||||
// constantOffset
|
||||
// );
|
||||
// }
|
||||
|
||||
// export function makeArraySet(
|
||||
// compiler: Compiler,
|
||||
// target: Class,
|
||||
// thisExpression: Expression,
|
||||
// elementExpression: Expression,
|
||||
// valueExpression: Expression,
|
||||
// contextualType: Type
|
||||
// ): ExpressionRef {
|
||||
// var type = assert(compiler.program.determineBuiltinArrayType(target));
|
||||
// return makeArraySetWithValue(
|
||||
// compiler,
|
||||
// target,
|
||||
// thisExpression,
|
||||
// elementExpression,
|
||||
// compiler.compileExpression(
|
||||
// valueExpression,
|
||||
// type.is(TypeFlags.INTEGER | TypeFlags.VALUE)
|
||||
// ? type.is(TypeFlags.LONG)
|
||||
// ? type.is(TypeFlags.SIGNED)
|
||||
// ? Type.i64
|
||||
// : Type.u64
|
||||
// : type.is(TypeFlags.SIGNED)
|
||||
// ? Type.i32
|
||||
// : Type.u32
|
||||
// : type,
|
||||
// ConversionKind.IMPLICIT,
|
||||
// WrapMode.NONE
|
||||
// ),
|
||||
// contextualType != Type.void
|
||||
// );
|
||||
// }
|
||||
|
||||
// export function makeArraySetWithValue(
|
||||
// compiler: Compiler,
|
||||
// target: Class,
|
||||
// thisExpression: Expression,
|
||||
// elementExpression: Expression,
|
||||
// valueExpr: ExpressionRef,
|
||||
// tee: bool
|
||||
// ): ExpressionRef {
|
||||
// var type = assert(compiler.program.determineBuiltinArrayType(target));
|
||||
// var module = compiler.module;
|
||||
|
||||
// var dataStart = assert(target.lookupInSelf("dataStart"));
|
||||
// assert(dataStart.kind == ElementKind.FIELD);
|
||||
// var dataLength = assert(target.lookupInSelf("dataLength"));
|
||||
// assert(dataLength.kind == ElementKind.FIELD);
|
||||
|
||||
// var constantOffset: i32 = 0;
|
||||
// var dynamicOffset = module.precomputeExpression(
|
||||
// compiler.compileExpression(
|
||||
// elementExpression,
|
||||
// Type.i32,
|
||||
// ConversionKind.IMPLICIT,
|
||||
// WrapMode.NONE
|
||||
// )
|
||||
// );
|
||||
// if (getExpressionId(dynamicOffset) == ExpressionId.Const) {
|
||||
// constantOffset = getConstValueI32(dynamicOffset);
|
||||
// dynamicOffset = 0;
|
||||
// } else if (getExpressionId(dynamicOffset) == ExpressionId.Binary) {
|
||||
// if (getBinaryOp(dynamicOffset) == BinaryOp.AddI32) {
|
||||
// let left = getBinaryLeft(dynamicOffset);
|
||||
// let right = getBinaryRight(dynamicOffset);
|
||||
// if (getExpressionId(left) == ExpressionId.Const) {
|
||||
// constantOffset = getConstValueI32(left);
|
||||
// dynamicOffset = right;
|
||||
// } else if (getExpressionId(right) == ExpressionId.Const) {
|
||||
// constantOffset = getConstValueI32(right);
|
||||
// dynamicOffset = left;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// var program = compiler.program;
|
||||
// var isManaged = type.isManaged(program) && target.type.isManaged(program);
|
||||
// var usizeType = compiler.options.usizeType;
|
||||
// var nativeSizeType = compiler.options.nativeSizeType;
|
||||
// var thisExpr = compiler.compileExpression(
|
||||
// thisExpression,
|
||||
// target.type,
|
||||
// ConversionKind.IMPLICIT,
|
||||
// WrapMode.NONE
|
||||
// );
|
||||
// var tempThis: Local | null = null;
|
||||
// if (isManaged) {
|
||||
// tempThis = compiler.currentFlow.getTempLocal(target.type, false);
|
||||
// thisExpr = module.createTeeLocal(tempThis.index, thisExpr);
|
||||
// }
|
||||
// var dataStartExpr = module.createLoad(usizeType.byteSize, true,
|
||||
// thisExpr, nativeSizeType, (<Field>dataStart).memoryOffset
|
||||
// );
|
||||
|
||||
// var typeAlignLog2 = type.alignLog2;
|
||||
// constantOffset <<= typeAlignLog2;
|
||||
// if (dynamicOffset) {
|
||||
// if (typeAlignLog2) {
|
||||
// dynamicOffset = module.createBinary(BinaryOp.ShlI32,
|
||||
// dynamicOffset,
|
||||
// module.createI32(typeAlignLog2)
|
||||
// );
|
||||
// }
|
||||
// if (nativeSizeType == NativeType.I64) {
|
||||
// dataStartExpr = module.createBinary(BinaryOp.AddI64,
|
||||
// dataStartExpr,
|
||||
// module.createUnary(UnaryOp.ExtendU32, dynamicOffset)
|
||||
// );
|
||||
// } else {
|
||||
// dataStartExpr = module.createBinary(BinaryOp.AddI32,
|
||||
// dataStartExpr,
|
||||
// dynamicOffset
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// // handle Array<Ref>: value = RETAIN<T, TArray>(value, this)
|
||||
// if (isManaged) {
|
||||
// let program = compiler.program;
|
||||
// let retainInstance = compiler.resolver.resolveFunction(assert(program.retainPrototype), [ type, target.type ]);
|
||||
// if (!retainInstance) return module.createUnreachable();
|
||||
// valueExpr = compiler.makeCallInlinePrechecked(retainInstance, [
|
||||
// valueExpr,
|
||||
// module.createGetLocal(assert(tempThis).index, nativeSizeType)
|
||||
// ], 0, true);
|
||||
|
||||
// // handle Uint8ClampedArray: value = ~(value >> 31) & (((255 - value) >> 31) | value)
|
||||
// } else if (target.internalName == BuiltinSymbols.Uint8ClampedArray) {
|
||||
// let tempLocal = compiler.currentFlow.getAndFreeTempLocal(Type.i32, true);
|
||||
// valueExpr = module.createBinary(BinaryOp.AndI32,
|
||||
// module.createBinary(BinaryOp.XorI32,
|
||||
// module.createBinary(BinaryOp.ShrI32,
|
||||
// module.createTeeLocal(tempLocal.index, valueExpr),
|
||||
// module.createI32(31)
|
||||
// ),
|
||||
// module.createI32(-1)
|
||||
// ),
|
||||
// module.createBinary(BinaryOp.OrI32,
|
||||
// module.createBinary(BinaryOp.ShrI32,
|
||||
// module.createBinary(BinaryOp.SubI32,
|
||||
// module.createI32(255),
|
||||
// module.createGetLocal(tempLocal.index, NativeType.I32)
|
||||
// ),
|
||||
// module.createI32(31)
|
||||
// ),
|
||||
// module.createGetLocal(tempLocal.index, NativeType.I32)
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
// assert(!tempThis);
|
||||
|
||||
// var nativeType = type.toNativeType();
|
||||
|
||||
// if (!tee) {
|
||||
// compiler.currentType = Type.void;
|
||||
// return module.createStore(
|
||||
// type.byteSize,
|
||||
// dataStartExpr,
|
||||
// valueExpr,
|
||||
// nativeType,
|
||||
// constantOffset
|
||||
// );
|
||||
// } else {
|
||||
// let flow = compiler.currentFlow;
|
||||
// let tempLocal = flow.getAndFreeTempLocal(type, false);
|
||||
// compiler.currentType = type;
|
||||
// return module.createBlock(null, [
|
||||
// module.createStore(
|
||||
// type.byteSize,
|
||||
// dataStartExpr,
|
||||
// module.createTeeLocal(tempLocal.index, valueExpr),
|
||||
// nativeType,
|
||||
// constantOffset
|
||||
// ),
|
||||
// module.createGetLocal(tempLocal.index, nativeType)
|
||||
// ], nativeType);
|
||||
// }
|
||||
// }
|
156
src/codegen/gc.ts
Normal file
156
src/codegen/gc.ts
Normal file
@ -0,0 +1,156 @@
|
||||
import { Compiler } from "../compiler";
|
||||
import { ExpressionRef, NativeType, BinaryOp } from "../module";
|
||||
import { Local, Function, Class } from "../program";
|
||||
import { Type } from "../types";
|
||||
|
||||
/** Prepares the insertion of a reference into an _uninitialized_ parent using the GC interface. */
|
||||
export function makeInsertRef(
|
||||
compiler: Compiler,
|
||||
valueExpr: ExpressionRef,
|
||||
tempParent: Local,
|
||||
nullable: bool
|
||||
): ExpressionRef {
|
||||
var module = compiler.module;
|
||||
var program = compiler.program;
|
||||
var usizeType = compiler.options.usizeType;
|
||||
var nativeSizeType = compiler.options.nativeSizeType;
|
||||
var flow = compiler.currentFlow;
|
||||
var tempValue = flow.getTempLocal(usizeType, false);
|
||||
var handle: ExpressionRef;
|
||||
var fn: Function | null;
|
||||
if (fn = program.linkRef) { // tracing
|
||||
handle = module.createCall(fn.internalName, [
|
||||
module.createGetLocal(tempValue.index, nativeSizeType),
|
||||
module.createGetLocal(tempParent.index, nativeSizeType)
|
||||
], NativeType.None);
|
||||
} else if (fn = program.retainRef) { // arc
|
||||
handle = module.createCall(fn.internalName, [
|
||||
module.createGetLocal(tempValue.index, nativeSizeType)
|
||||
], NativeType.None);
|
||||
} else {
|
||||
assert(false);
|
||||
return module.createUnreachable();
|
||||
}
|
||||
flow.freeTempLocal(tempValue);
|
||||
if (!compiler.compileFunction(fn)) return module.createUnreachable();
|
||||
// {
|
||||
// [if (value !== null)] link/retain(value[, parent])
|
||||
// } -> value
|
||||
return module.createBlock(null, [
|
||||
module.createSetLocal(tempValue.index, valueExpr),
|
||||
nullable
|
||||
? module.createIf(
|
||||
module.createGetLocal(tempValue.index, nativeSizeType),
|
||||
handle
|
||||
)
|
||||
: handle,
|
||||
module.createGetLocal(tempValue.index, nativeSizeType)
|
||||
], nativeSizeType);
|
||||
}
|
||||
|
||||
/** Prepares the replaces a reference hold by an _initialized_ parent using the GC interface. */
|
||||
export function makeReplaceRef(
|
||||
compiler: Compiler,
|
||||
valueExpr: ExpressionRef,
|
||||
oldValueExpr: ExpressionRef,
|
||||
tempParent: Local,
|
||||
nullable: bool
|
||||
): ExpressionRef {
|
||||
var module = compiler.module;
|
||||
var program = compiler.program;
|
||||
var usizeType = compiler.options.usizeType;
|
||||
var nativeSizeType = compiler.options.nativeSizeType;
|
||||
var flow = compiler.currentFlow;
|
||||
var tempValue = flow.getTempLocal(usizeType, false);
|
||||
var tempOldValue = flow.getTempLocal(usizeType, false);
|
||||
var handleOld: ExpressionRef;
|
||||
var handleNew: ExpressionRef;
|
||||
var fn1: Function | null, fn2: Function | null;
|
||||
if (fn1 = program.linkRef) { // tracing
|
||||
fn2 = assert(program.unlinkRef);
|
||||
handleOld = module.createCall(fn2.internalName, [
|
||||
module.createGetLocal(tempOldValue.index, nativeSizeType),
|
||||
module.createGetLocal(tempParent.index, nativeSizeType)
|
||||
], NativeType.None);
|
||||
handleNew = module.createCall(fn1.internalName, [
|
||||
module.createGetLocal(tempValue.index, nativeSizeType),
|
||||
module.createGetLocal(tempParent.index, nativeSizeType)
|
||||
], NativeType.None);
|
||||
} else if (fn1 = program.retainRef) { // arc
|
||||
fn2 = assert(program.releaseRef);
|
||||
handleOld = module.createCall(fn2.internalName, [
|
||||
module.createGetLocal(tempOldValue.index, nativeSizeType)
|
||||
], NativeType.None);
|
||||
handleNew = module.createCall(fn1.internalName, [
|
||||
module.createGetLocal(tempValue.index, nativeSizeType)
|
||||
], NativeType.None);
|
||||
} else {
|
||||
assert(false);
|
||||
return module.createUnreachable();
|
||||
}
|
||||
flow.freeTempLocal(tempValue);
|
||||
flow.freeTempLocal(tempOldValue);
|
||||
if (!compiler.compileFunction(fn1) || !compiler.compileFunction(fn2)) return module.createUnreachable();
|
||||
// if (value != oldValue) {
|
||||
// if (oldValue !== null) unlink/release(oldValue[, parent])
|
||||
// [if (value !== null)] link/retain(value[, parent])
|
||||
// } -> value
|
||||
return module.createIf(
|
||||
module.createBinary(nativeSizeType == NativeType.I32 ? BinaryOp.NeI32 : BinaryOp.NeI64,
|
||||
module.createTeeLocal(tempValue.index, valueExpr),
|
||||
module.createTeeLocal(tempOldValue.index, oldValueExpr)
|
||||
),
|
||||
module.createBlock(null, [
|
||||
module.createIf(
|
||||
module.createGetLocal(tempOldValue.index, nativeSizeType),
|
||||
handleOld
|
||||
),
|
||||
nullable
|
||||
? module.createIf(
|
||||
module.createGetLocal(tempValue.index, nativeSizeType),
|
||||
handleNew
|
||||
)
|
||||
: handleNew,
|
||||
module.createGetLocal(tempValue.index, nativeSizeType)
|
||||
], nativeSizeType),
|
||||
module.createGetLocal(tempValue.index, nativeSizeType)
|
||||
);
|
||||
}
|
||||
|
||||
export function makeInstanceOfClass(
|
||||
compiler: Compiler,
|
||||
expr: ExpressionRef,
|
||||
classInstance: Class
|
||||
): ExpressionRef {
|
||||
var module = compiler.module;
|
||||
var flow = compiler.currentFlow;
|
||||
var idTemp = flow.getTempLocal(Type.i32, false);
|
||||
var idExpr = module.createLoad(4, false,
|
||||
module.createBinary(BinaryOp.SubI32,
|
||||
expr,
|
||||
module.createI32(compiler.program.runtimeHeaderSize)
|
||||
),
|
||||
NativeType.I32
|
||||
);
|
||||
var label = "instanceof_" + classInstance.name + "|" + flow.pushBreakLabel();
|
||||
var conditions: ExpressionRef[] = [];
|
||||
conditions.push(
|
||||
module.createDrop( // br_if returns the value too
|
||||
module.createBreak(label,
|
||||
module.createBinary(BinaryOp.EqI32, // classId == class.id
|
||||
module.createTeeLocal(idTemp.index, idExpr),
|
||||
module.createI32(classInstance.id)
|
||||
),
|
||||
module.createI32(1) // ? true
|
||||
)
|
||||
)
|
||||
);
|
||||
// TODO: insert conditions for all possible subclasses (i.e. cat is also animal)
|
||||
// TODO: simplify if there are none
|
||||
conditions.push(
|
||||
module.createI32(0) // : false
|
||||
);
|
||||
flow.freeTempLocal(idTemp);
|
||||
flow.popBreakLabel();
|
||||
return module.createBlock(label, conditions, NativeType.I32);
|
||||
}
|
@ -189,6 +189,8 @@ export namespace LibrarySymbols {
|
||||
export const REGISTER = "REGISTER";
|
||||
export const RETAIN = "RETAIN";
|
||||
export const RELEASE = "RELEASE";
|
||||
export const MOVE = "MOVE";
|
||||
export const REPLACE = "REPLACE";
|
||||
export const MAKEARRAY = "MAKEARRAY";
|
||||
// other
|
||||
export const length = "length";
|
||||
|
382
src/compiler.ts
382
src/compiler.ts
@ -7,10 +7,7 @@ import {
|
||||
compileCall as compileBuiltinCall,
|
||||
compileAbort,
|
||||
compileIterateRoots,
|
||||
BuiltinSymbols,
|
||||
compileBuiltinArrayGet,
|
||||
compileBuiltinArraySet,
|
||||
compileBuiltinArraySetWithValue
|
||||
BuiltinSymbols
|
||||
} from "./builtins";
|
||||
|
||||
import {
|
||||
@ -174,6 +171,8 @@ import {
|
||||
makeMap
|
||||
} from "./util";
|
||||
|
||||
import { makeInsertRef, makeReplaceRef } from "./codegen/gc";
|
||||
|
||||
/** Compilation target. */
|
||||
export enum Target {
|
||||
/** WebAssembly with 32-bit pointers. */
|
||||
@ -1010,7 +1009,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
if (initInStart) {
|
||||
module.addGlobal(val.internalName, NativeType.I32, true, module.createI32(0));
|
||||
this.currentBody.push(
|
||||
module.createSetGlobal(val.internalName, initExpr)
|
||||
this.makeGlobalAssignment(val, initExpr, false)
|
||||
);
|
||||
previousValueIsMut = true;
|
||||
} else {
|
||||
@ -4745,19 +4744,19 @@ export class Compiler extends DiagnosticEmitter {
|
||||
let elementExpression = resolver.currentElementExpression;
|
||||
if (elementExpression) { // indexed access
|
||||
let isUnchecked = flow.is(FlowFlags.UNCHECKED_CONTEXT);
|
||||
if (isUnchecked) {
|
||||
let arrayType = this.program.determineBuiltinArrayType(<Class>target);
|
||||
if (arrayType) {
|
||||
return compileBuiltinArraySet(
|
||||
this,
|
||||
<Class>target,
|
||||
assert(this.resolver.currentThisExpression),
|
||||
elementExpression,
|
||||
valueExpression,
|
||||
contextualType
|
||||
);
|
||||
}
|
||||
}
|
||||
// if (isUnchecked) {
|
||||
// let arrayType = this.program.determineBuiltinArrayType(<Class>target);
|
||||
// if (arrayType) {
|
||||
// return compileBuiltinArraySet(
|
||||
// this,
|
||||
// <Class>target,
|
||||
// assert(this.resolver.currentThisExpression),
|
||||
// elementExpression,
|
||||
// valueExpression,
|
||||
// contextualType
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
let indexedSet = (<Class>target).lookupOverload(OperatorKind.INDEXED_SET, isUnchecked);
|
||||
if (!indexedSet) {
|
||||
let indexedGet = (<Class>target).lookupOverload(OperatorKind.INDEXED_GET, isUnchecked);
|
||||
@ -4801,7 +4800,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
compileAssignmentWithValue(
|
||||
expression: Expression,
|
||||
valueWithCorrectType: ExpressionRef,
|
||||
valueExpr: ExpressionRef,
|
||||
tee: bool = false
|
||||
): ExpressionRef {
|
||||
var module = this.module;
|
||||
@ -4811,49 +4810,28 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
switch (target.kind) {
|
||||
case ElementKind.LOCAL: {
|
||||
let type = (<Local>target).type;
|
||||
assert(type != Type.void);
|
||||
this.currentType = tee ? type : Type.void;
|
||||
if ((<Local>target).is(CommonFlags.CONST)) {
|
||||
if (target.is(CommonFlags.CONST)) {
|
||||
this.error(
|
||||
DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property,
|
||||
expression.range, target.internalName
|
||||
);
|
||||
this.currentType = tee ? (<Local>target).type : Type.void;
|
||||
return module.createUnreachable();
|
||||
}
|
||||
let localIndex = (<Local>target).index;
|
||||
if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) {
|
||||
if (!flow.canOverflow(valueWithCorrectType, type)) flow.setLocalFlag(localIndex, LocalFlags.WRAPPED);
|
||||
else flow.unsetLocalFlag(localIndex, LocalFlags.WRAPPED);
|
||||
}
|
||||
return tee
|
||||
? module.createTeeLocal(localIndex, valueWithCorrectType)
|
||||
: module.createSetLocal(localIndex, valueWithCorrectType);
|
||||
return this.makeLocalAssignment(<Local>target, valueExpr, tee);
|
||||
}
|
||||
case ElementKind.GLOBAL: {
|
||||
if (!this.compileGlobal(<Global>target)) return module.createUnreachable();
|
||||
let type = (<Global>target).type;
|
||||
assert(type != Type.void);
|
||||
this.currentType = tee ? type : Type.void;
|
||||
if ((<Local>target).is(CommonFlags.CONST)) {
|
||||
if (target.is(CommonFlags.CONST)) {
|
||||
this.error(
|
||||
DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property,
|
||||
expression.range,
|
||||
target.internalName
|
||||
);
|
||||
this.currentType = tee ? (<Global>target).type : Type.void;
|
||||
return module.createUnreachable();
|
||||
}
|
||||
valueWithCorrectType = this.ensureSmallIntegerWrap(valueWithCorrectType, type); // guaranteed
|
||||
if (tee) {
|
||||
let nativeType = type.toNativeType();
|
||||
let internalName = target.internalName;
|
||||
return module.createBlock(null, [ // emulated teeGlobal
|
||||
module.createSetGlobal(internalName, valueWithCorrectType),
|
||||
module.createGetGlobal(internalName, nativeType)
|
||||
], nativeType);
|
||||
} else {
|
||||
return module.createSetGlobal(target.internalName, valueWithCorrectType);
|
||||
}
|
||||
return this.makeGlobalAssignment(<Global>target, valueExpr, tee);
|
||||
}
|
||||
case ElementKind.FIELD: {
|
||||
let initializerNode = (<Field>target).initializerNode;
|
||||
@ -4870,66 +4848,15 @@ export class Compiler extends DiagnosticEmitter {
|
||||
);
|
||||
return module.createUnreachable();
|
||||
}
|
||||
let thisExpression = assert(this.resolver.currentThisExpression);
|
||||
let thisExpr = this.compileExpressionRetainType(
|
||||
thisExpression,
|
||||
this.options.usizeType,
|
||||
WrapMode.NONE
|
||||
return this.makeFieldAssignment(<Field>target,
|
||||
valueExpr,
|
||||
this.compileExpressionRetainType(
|
||||
assert(this.resolver.currentThisExpression),
|
||||
// FIXME: explicit type (currently fails due to missing null checking)
|
||||
this.options.usizeType, WrapMode.NONE
|
||||
),
|
||||
tee
|
||||
);
|
||||
let thisType = this.currentType;
|
||||
let type = (<Field>target).type;
|
||||
let nativeType = type.toNativeType();
|
||||
if (type.kind == TypeKind.BOOL) {
|
||||
// make sure bools are wrapped (usually are) when storing as 8 bits
|
||||
valueWithCorrectType = this.ensureSmallIntegerWrap(valueWithCorrectType, type);
|
||||
}
|
||||
let program = this.program;
|
||||
let tempThis: Local | null = null;
|
||||
if (type.isManaged(program) && thisType.isManaged(program)) {
|
||||
let retainInstance = this.resolver.resolveFunction(assert(program.retainPrototype), [ type, thisType ]);
|
||||
if (!retainInstance) {
|
||||
this.currentType = tee ? type : Type.void;
|
||||
return module.createUnreachable();
|
||||
}
|
||||
tempThis = this.currentFlow.getTempLocal(thisType, false);
|
||||
// this = (tempThis = this)
|
||||
thisExpr = module.createTeeLocal(tempThis.index, thisExpr);
|
||||
// value = RETAIN(value, tempThis)
|
||||
valueWithCorrectType = this.makeCallInlinePrechecked(retainInstance, [
|
||||
valueWithCorrectType,
|
||||
module.createGetLocal(tempThis.index, this.options.nativeSizeType)
|
||||
], 0, true);
|
||||
}
|
||||
if (tee) {
|
||||
let tempValue = this.currentFlow.getAndFreeTempLocal(
|
||||
type,
|
||||
!flow.canOverflow(valueWithCorrectType, type)
|
||||
);
|
||||
if (tempThis) this.currentFlow.freeTempLocal(tempThis);
|
||||
this.currentType = type;
|
||||
// (this.field = (tempValue = value)), tempValue
|
||||
return module.createBlock(null, [
|
||||
module.createStore(
|
||||
type.byteSize,
|
||||
thisExpr,
|
||||
module.createTeeLocal(tempValue.index, valueWithCorrectType),
|
||||
nativeType,
|
||||
(<Field>target).memoryOffset
|
||||
),
|
||||
module.createGetLocal(tempValue.index, nativeType)
|
||||
], nativeType);
|
||||
} else {
|
||||
if (tempThis) this.currentFlow.freeTempLocal(tempThis);
|
||||
this.currentType = Type.void;
|
||||
// this.field = value
|
||||
return module.createStore(
|
||||
type.byteSize,
|
||||
thisExpr,
|
||||
valueWithCorrectType,
|
||||
nativeType,
|
||||
(<Field>target).memoryOffset
|
||||
);
|
||||
}
|
||||
}
|
||||
case ElementKind.PROPERTY_PROTOTYPE: { // static property
|
||||
let setterPrototype = (<PropertyPrototype>target).setterPrototype;
|
||||
@ -4943,7 +4870,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
let setterInstance = this.resolver.resolveFunction(setterPrototype, null, makeMap(), ReportMode.REPORT);
|
||||
if (!setterInstance) return module.createUnreachable();
|
||||
// call just the setter if the return value isn't of interest
|
||||
if (!tee) return this.makeCallDirect(setterInstance, [ valueWithCorrectType ], expression);
|
||||
if (!tee) return this.makeCallDirect(setterInstance, [ valueExpr ], expression);
|
||||
// otherwise call the setter first, then the getter
|
||||
let getterPrototype = assert((<PropertyPrototype>target).getterPrototype); // must be present
|
||||
let getterInstance = this.resolver.resolveFunction(getterPrototype, null, makeMap(), ReportMode.REPORT);
|
||||
@ -4951,7 +4878,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
let returnType = getterInstance.signature.returnType;
|
||||
let nativeReturnType = returnType.toNativeType();
|
||||
return module.createBlock(null, [
|
||||
this.makeCallDirect(setterInstance, [ valueWithCorrectType ], expression),
|
||||
this.makeCallDirect(setterInstance, [ valueExpr ], expression),
|
||||
this.makeCallDirect(getterInstance, null, expression) // sets currentType
|
||||
], nativeReturnType);
|
||||
}
|
||||
@ -4968,10 +4895,9 @@ export class Compiler extends DiagnosticEmitter {
|
||||
if (!tee) {
|
||||
let thisExpr = this.compileExpressionRetainType(
|
||||
assert(this.resolver.currentThisExpression),
|
||||
this.options.usizeType,
|
||||
WrapMode.NONE
|
||||
this.options.usizeType, WrapMode.NONE
|
||||
);
|
||||
return this.makeCallDirect(setterInstance, [ thisExpr, valueWithCorrectType ], expression);
|
||||
return this.makeCallDirect(setterInstance, [ thisExpr, valueExpr ], expression);
|
||||
}
|
||||
// otherwise call the setter first, then the getter
|
||||
let getterInstance = assert((<Property>target).getterInstance); // must be present
|
||||
@ -4987,7 +4913,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
return module.createBlock(null, [
|
||||
this.makeCallDirect(setterInstance, [ // set and remember the target
|
||||
module.createTeeLocal(tempLocalIndex, thisExpr),
|
||||
valueWithCorrectType
|
||||
valueExpr
|
||||
], expression),
|
||||
this.makeCallDirect(getterInstance, [ // get from remembered target
|
||||
module.createGetLocal(tempLocalIndex, nativeReturnType)
|
||||
@ -4998,19 +4924,19 @@ export class Compiler extends DiagnosticEmitter {
|
||||
let elementExpression = this.resolver.currentElementExpression;
|
||||
if (elementExpression) {
|
||||
let isUnchecked = flow.is(FlowFlags.UNCHECKED_CONTEXT);
|
||||
if (isUnchecked) {
|
||||
let arrayType = this.program.determineBuiltinArrayType(<Class>target);
|
||||
if (arrayType) {
|
||||
return compileBuiltinArraySetWithValue(
|
||||
this,
|
||||
<Class>target,
|
||||
assert(this.resolver.currentThisExpression),
|
||||
elementExpression,
|
||||
valueWithCorrectType,
|
||||
tee
|
||||
);
|
||||
}
|
||||
}
|
||||
// if (isUnchecked) {
|
||||
// let arrayType = this.program.determineBuiltinArrayType(<Class>target);
|
||||
// if (arrayType) {
|
||||
// return compileBuiltinArraySetWithValue(
|
||||
// this,
|
||||
// <Class>target,
|
||||
// assert(this.resolver.currentThisExpression),
|
||||
// elementExpression,
|
||||
// valueExpr,
|
||||
// tee
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
let indexedGet = (<Class>target).lookupOverload(OperatorKind.INDEXED_GET, isUnchecked);
|
||||
if (!indexedGet) {
|
||||
this.error(
|
||||
@ -5050,7 +4976,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
this.makeCallDirect(indexedSet, [
|
||||
module.createTeeLocal(tempLocalTarget.index, thisExpr),
|
||||
module.createTeeLocal(tempLocalElement.index, elementExpr),
|
||||
valueWithCorrectType
|
||||
valueExpr
|
||||
], expression),
|
||||
this.makeCallDirect(indexedGet, [
|
||||
module.createGetLocal(tempLocalTarget.index, tempLocalTarget.type.toNativeType()),
|
||||
@ -5061,7 +4987,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
return this.makeCallDirect(indexedSet, [
|
||||
thisExpr,
|
||||
elementExpr,
|
||||
valueWithCorrectType
|
||||
valueExpr
|
||||
], expression);
|
||||
}
|
||||
}
|
||||
@ -5075,6 +5001,126 @@ export class Compiler extends DiagnosticEmitter {
|
||||
return module.createUnreachable();
|
||||
}
|
||||
|
||||
makeLocalAssignment(local: Local, valueExpr: ExpressionRef, tee: bool): ExpressionRef {
|
||||
// TBD: use REPLACE macro to keep track of managed refcounts? or can the compiler evaluate
|
||||
// this statically in closed contexts like functions in order to safe the extra work?
|
||||
var type = local.type;
|
||||
assert(type != Type.void);
|
||||
var localIndex = local.index;
|
||||
if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) {
|
||||
let flow = this.currentFlow;
|
||||
if (!flow.canOverflow(valueExpr, type)) flow.setLocalFlag(localIndex, LocalFlags.WRAPPED);
|
||||
else flow.unsetLocalFlag(localIndex, LocalFlags.WRAPPED);
|
||||
}
|
||||
if (tee) {
|
||||
this.currentType = type;
|
||||
return this.module.createTeeLocal(localIndex, valueExpr);
|
||||
} else {
|
||||
this.currentType = Type.void;
|
||||
return this.module.createSetLocal(localIndex, valueExpr)
|
||||
}
|
||||
}
|
||||
|
||||
makeGlobalAssignment(global: Global, valueExpr: ExpressionRef, tee: bool): ExpressionRef {
|
||||
// TBD: use REPLACE macro to keep track of managed refcounts? currently this doesn't work
|
||||
// because there isn't a parent ref here. a tracing GC wouldn't need the hook at all while
|
||||
// a reference counting gc doesn't need a parent.
|
||||
var type = global.type;
|
||||
assert(type != Type.void);
|
||||
valueExpr = this.ensureSmallIntegerWrap(valueExpr, type); // global values must be wrapped
|
||||
if (tee) {
|
||||
let module = this.module;
|
||||
let nativeType = type.toNativeType();
|
||||
let tempValue = this.currentFlow.getAndFreeTempLocal(type, true);
|
||||
this.currentType = type;
|
||||
return module.createBlock(null, [
|
||||
module.createSetGlobal(global.internalName,
|
||||
module.createTeeLocal(tempValue.index, valueExpr)
|
||||
),
|
||||
module.createGetLocal(tempValue.index, nativeType)
|
||||
], nativeType);
|
||||
} else {
|
||||
this.currentType = Type.void;
|
||||
return this.module.createSetGlobal(global.internalName, valueExpr);
|
||||
}
|
||||
}
|
||||
|
||||
makeFieldAssignment(field: Field, valueExpr: ExpressionRef, thisExpr: ExpressionRef, tee: bool): ExpressionRef {
|
||||
var program = this.program;
|
||||
var module = this.module;
|
||||
var flow = this.currentFlow;
|
||||
var fieldType = field.type;
|
||||
var nativeFieldType = fieldType.toNativeType();
|
||||
assert(field.parent.kind == ElementKind.CLASS);
|
||||
var thisType = (<Class>field.parent).type;
|
||||
var nativeThisType = thisType.toNativeType();
|
||||
|
||||
// MANAGED: this.field = replace(value, this.field)
|
||||
if (fieldType.isManaged(program)) {
|
||||
let tempThis = flow.getTempLocal(thisType, false);
|
||||
let expr: ExpressionRef;
|
||||
if (tee) { // tee value to a temp local and make it the block's result
|
||||
let tempValue = flow.getTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType));
|
||||
expr = module.createBlock(null, [
|
||||
module.createStore(fieldType.byteSize,
|
||||
module.createTeeLocal(tempThis.index, thisExpr),
|
||||
makeReplaceRef(this,
|
||||
module.createTeeLocal(tempValue.index, valueExpr),
|
||||
module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED),
|
||||
module.createGetLocal(tempThis.index, nativeThisType),
|
||||
nativeFieldType, field.memoryOffset
|
||||
),
|
||||
tempThis,
|
||||
fieldType.is(TypeFlags.NULLABLE)
|
||||
),
|
||||
nativeFieldType, field.memoryOffset
|
||||
),
|
||||
module.createGetLocal(tempValue.index, nativeFieldType)
|
||||
], nativeFieldType);
|
||||
flow.freeTempLocal(tempValue);
|
||||
this.currentType = fieldType;
|
||||
} else { // no need for a temp local
|
||||
expr = module.createStore(fieldType.byteSize,
|
||||
module.createTeeLocal(tempThis.index, thisExpr),
|
||||
makeReplaceRef(this,
|
||||
valueExpr,
|
||||
module.createLoad(fieldType.byteSize, fieldType.is(TypeFlags.SIGNED),
|
||||
module.createGetLocal(tempThis.index, nativeThisType),
|
||||
nativeFieldType, field.memoryOffset
|
||||
),
|
||||
tempThis,
|
||||
fieldType.is(TypeFlags.NULLABLE)
|
||||
),
|
||||
nativeFieldType, field.memoryOffset
|
||||
);
|
||||
this.currentType = Type.void;
|
||||
}
|
||||
flow.freeTempLocal(tempThis);
|
||||
return expr;
|
||||
}
|
||||
|
||||
// UNMANAGED: this.field = value
|
||||
if (tee) {
|
||||
this.currentType = fieldType;
|
||||
let tempValue = flow.getAndFreeTempLocal(fieldType, !flow.canOverflow(valueExpr, fieldType));
|
||||
return module.createBlock(null, [
|
||||
module.createStore(fieldType.byteSize,
|
||||
thisExpr,
|
||||
module.createTeeLocal(tempValue.index, valueExpr),
|
||||
nativeFieldType, field.memoryOffset
|
||||
),
|
||||
module.createGetLocal(tempValue.index, nativeFieldType)
|
||||
], nativeFieldType);
|
||||
} else {
|
||||
this.currentType = Type.void;
|
||||
return module.createStore(fieldType.byteSize,
|
||||
thisExpr,
|
||||
valueExpr,
|
||||
nativeFieldType, field.memoryOffset
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
compileCallExpression(
|
||||
expression: CallExpression,
|
||||
contextualType: Type,
|
||||
@ -6072,18 +6118,18 @@ export class Compiler extends DiagnosticEmitter {
|
||||
switch (target.kind) {
|
||||
case ElementKind.CLASS: {
|
||||
let isUnchecked = this.currentFlow.is(FlowFlags.UNCHECKED_CONTEXT);
|
||||
if (isUnchecked) {
|
||||
let arrayType = this.program.determineBuiltinArrayType(<Class>target);
|
||||
if (arrayType) {
|
||||
return compileBuiltinArrayGet(
|
||||
this,
|
||||
<Class>target,
|
||||
expression.expression,
|
||||
expression.elementExpression,
|
||||
contextualType
|
||||
);
|
||||
}
|
||||
}
|
||||
// if (isUnchecked) {
|
||||
// let arrayType = this.program.determineBuiltinArrayType(<Class>target);
|
||||
// if (arrayType) {
|
||||
// return compileBuiltinArrayGet(
|
||||
// this,
|
||||
// <Class>target,
|
||||
// expression.expression,
|
||||
// expression.elementExpression,
|
||||
// contextualType
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
let indexedGet = (<Class>target).lookupOverload(OperatorKind.INDEXED_GET, isUnchecked);
|
||||
if (!indexedGet) {
|
||||
this.error(
|
||||
@ -6814,26 +6860,19 @@ export class Compiler extends DiagnosticEmitter {
|
||||
)
|
||||
)
|
||||
);
|
||||
var isManaged = elementType.isManaged(program) && arrayType.isManaged(program);
|
||||
var retainInstance = isManaged
|
||||
? this.resolver.resolveFunction(assert(program.retainPrototype), [ elementType, arrayType ])
|
||||
: null;
|
||||
var isManaged = elementType.isManaged(program);
|
||||
for (let i = 0, alignLog2 = elementType.alignLog2; i < length; ++i) {
|
||||
let valueExpression = expressions[i];
|
||||
let valueExpr = valueExpression
|
||||
? this.compileExpression(valueExpression, elementType, ConversionKind.IMPLICIT, WrapMode.NONE)
|
||||
: elementType.toNativeZero(module);
|
||||
if (isManaged) {
|
||||
if (!retainInstance) {
|
||||
valueExpr = module.createUnreachable();
|
||||
} else {
|
||||
// value = RETAIN(value, tempThis)
|
||||
valueExpr = this.makeCallInlinePrechecked(retainInstance, [
|
||||
valueExpr,
|
||||
module.createGetLocal(tempThis.index, nativeArrayType)
|
||||
], 0, true);
|
||||
this.currentFlow = flow;
|
||||
}
|
||||
// value = link/retain(value[, tempThis])
|
||||
valueExpr = makeInsertRef(this,
|
||||
valueExpr,
|
||||
tempThis,
|
||||
elementType.is(TypeFlags.NULLABLE)
|
||||
);
|
||||
}
|
||||
// store<T>(tempData, value, immOffset)
|
||||
stmts.push(
|
||||
@ -6849,7 +6888,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
stmts.push(
|
||||
module.createGetLocal(tempThis.index, nativeArrayType)
|
||||
);
|
||||
flow.freeTempLocal(tempThis); // but can be reused now
|
||||
flow.freeTempLocal(tempThis);
|
||||
flow.freeTempLocal(tempDataStart);
|
||||
this.currentType = arrayType;
|
||||
return module.createBlock(null, stmts, nativeArrayType);
|
||||
@ -7212,43 +7251,6 @@ export class Compiler extends DiagnosticEmitter {
|
||||
return module.createUnreachable();
|
||||
}
|
||||
|
||||
private compileGetter(target: PropertyPrototype, reportNode: Node): ExpressionRef {
|
||||
var prototype = target.getterPrototype;
|
||||
if (prototype) {
|
||||
let instance = this.resolver.resolveFunction(prototype, null);
|
||||
if (!instance) return this.module.createUnreachable();
|
||||
let signature = instance.signature;
|
||||
if (!this.checkCallSignature( // reports
|
||||
signature,
|
||||
0,
|
||||
instance.is(CommonFlags.INSTANCE),
|
||||
reportNode
|
||||
)) {
|
||||
return this.module.createUnreachable();
|
||||
}
|
||||
if (instance.is(CommonFlags.INSTANCE)) {
|
||||
let classInstance = assert(instance.parent); assert(classInstance.kind == ElementKind.CLASS);
|
||||
let thisExpression = assert(this.resolver.currentThisExpression); //!!!
|
||||
let thisExpr = this.compileExpressionRetainType(
|
||||
thisExpression,
|
||||
this.options.usizeType,
|
||||
WrapMode.NONE
|
||||
);
|
||||
this.currentType = signature.returnType;
|
||||
return this.compileCallDirect(instance, [], reportNode, thisExpr);
|
||||
} else {
|
||||
this.currentType = signature.returnType;
|
||||
return this.compileCallDirect(instance, [], reportNode, 0);
|
||||
}
|
||||
} else {
|
||||
this.error(
|
||||
DiagnosticCode.Property_0_does_not_exist_on_type_1,
|
||||
reportNode.range, (<PropertyPrototype>target).name, (<PropertyPrototype>target).parent.toString()
|
||||
);
|
||||
return this.module.createUnreachable();
|
||||
}
|
||||
}
|
||||
|
||||
compileTernaryExpression(expression: TernaryExpression, contextualType: Type): ExpressionRef {
|
||||
var ifThen = expression.ifThen;
|
||||
var ifElse = expression.ifElse;
|
||||
|
27
src/flow.ts
27
src/flow.ts
@ -156,7 +156,30 @@ export enum LocalFlags {
|
||||
export namespace LocalFlags {
|
||||
export function join(left: LocalFlags, right: LocalFlags): LocalFlags {
|
||||
return ((left & LocalFlags.ANY_CATEGORICAL) & (right & LocalFlags.ANY_CATEGORICAL))
|
||||
| (left & LocalFlags.ANY_CONDITIONAL) | (right & LocalFlags.ANY_CONDITIONAL);
|
||||
| (left & LocalFlags.ANY_CONDITIONAL) | (right & LocalFlags.ANY_CONDITIONAL);
|
||||
}
|
||||
}
|
||||
|
||||
/** Flags indicating the current state of a field. */
|
||||
export enum FieldFlags {
|
||||
/** No specific conditions. */
|
||||
NONE = 0,
|
||||
|
||||
/** Field is initialized. Relevant in constructors. */
|
||||
INITIALIZED = 1 << 0,
|
||||
/** Field is conditionally initialized. Relevant in constructors. */
|
||||
CONDITIONALLY_INITIALIZED = 1 << 1,
|
||||
|
||||
/** Any categorical flag. */
|
||||
ANY_CATEGORICAL = INITIALIZED,
|
||||
|
||||
/** Any conditional flag. */
|
||||
ANY_CONDITIONAL = CONDITIONALLY_INITIALIZED
|
||||
}
|
||||
export namespace FieldFlags {
|
||||
export function join(left: FieldFlags, right: FieldFlags): FieldFlags {
|
||||
return ((left & FieldFlags.ANY_CATEGORICAL) & (right & FieldFlags.ANY_CATEGORICAL))
|
||||
| (left & FieldFlags.ANY_CONDITIONAL) | (right & FieldFlags.ANY_CONDITIONAL);
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,6 +204,8 @@ export class Flow {
|
||||
scopedLocals: Map<string,Local> | null = null;
|
||||
/** Local flags. */
|
||||
localFlags: LocalFlags[];
|
||||
/** Field flags. Relevant in constructors. */
|
||||
fieldFlags: Map<string,FieldFlags> | null = null;
|
||||
/** Function being inlined, when inlining. */
|
||||
inlineFunction: Function | null;
|
||||
/** The label we break to when encountering a return statement, when inlining. */
|
||||
|
@ -354,23 +354,25 @@ export class Program extends DiagnosticEmitter {
|
||||
stringInstance: Class | null = null;
|
||||
/** Abort function reference, if present. */
|
||||
abortInstance: Function | null = null;
|
||||
/** Runtime allocation function. */
|
||||
|
||||
/** Runtime allocation macro. `ALLOCATE(payloadSize: usize): usize` */
|
||||
allocateInstance: Function | null = null;
|
||||
/** Unmanaged allocation function. */
|
||||
/** Unmanaged allocation macro. `ALLOCATE_UNMANAGED(size: usize): usize` */
|
||||
allocateUnmanagedInstance: Function | null = null;
|
||||
/** Runtime reallocation function. */
|
||||
/** Runtime reallocation macro. `REALLOCATE(ref: usize, newPayloadSize: usize): usize` */
|
||||
reallocateInstance: Function | null = null;
|
||||
/** Runtime discard function. */
|
||||
/** Runtime discard macro. `DISCARD(ref: usize): void` */
|
||||
discardInstance: Function | null = null;
|
||||
/** Runtime register function. */
|
||||
/** Runtime register macro. `REGISTER<T>(ref: usize): T` */
|
||||
registerPrototype: FunctionPrototype | null = null;
|
||||
/** Runtime retain function. */
|
||||
retainPrototype: FunctionPrototype | null = null;
|
||||
/** Runtime release function. */
|
||||
releasePrototype: FunctionPrototype | null = null;
|
||||
/** Runtime make array function. */
|
||||
/** Runtime make array macro. `MAKEARRAY<V>(capacity: i32, source: usize = 0): Array<V>` */
|
||||
makeArrayPrototype: FunctionPrototype | null = null;
|
||||
|
||||
linkRef: Function | null = null;
|
||||
unlinkRef: Function | null = null;
|
||||
retainRef: Function | null = null;
|
||||
releaseRef: Function | null = null;
|
||||
|
||||
/** Next class id. */
|
||||
nextClassId: u32 = 1;
|
||||
|
||||
@ -378,7 +380,7 @@ export class Program extends DiagnosticEmitter {
|
||||
|
||||
/** Whether a garbage collector is present or not. */
|
||||
get gcImplemented(): bool {
|
||||
return this.lookupGlobal("__gc_register") !== null;
|
||||
return this.lookupGlobal("__ref_collect") !== null;
|
||||
}
|
||||
/** Garbage collector mark function called to on reachable managed objects. */
|
||||
gcMarkInstance: Function | null = null; // FIXME
|
||||
@ -830,18 +832,25 @@ export class Program extends DiagnosticEmitter {
|
||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
||||
this.registerPrototype = <FunctionPrototype>element;
|
||||
}
|
||||
if (element = this.lookupGlobal(LibrarySymbols.RETAIN)) {
|
||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
||||
this.retainPrototype = <FunctionPrototype>element;
|
||||
}
|
||||
if (element = this.lookupGlobal(LibrarySymbols.RELEASE)) {
|
||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
||||
this.releasePrototype = <FunctionPrototype>element;
|
||||
}
|
||||
if (element = this.lookupGlobal(LibrarySymbols.MAKEARRAY)) {
|
||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
||||
this.makeArrayPrototype = <FunctionPrototype>element;
|
||||
}
|
||||
if (this.lookupGlobal("__ref_collect")) {
|
||||
if (element = this.lookupGlobal("__ref_link")) {
|
||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
||||
this.linkRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
||||
element = assert(this.lookupGlobal("__ref_unlink"));
|
||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
||||
this.unlinkRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
||||
} else if (element = this.lookupGlobal("__ref_retain")) {
|
||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
||||
this.retainRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
||||
element = assert(this.lookupGlobal("__ref_release"));
|
||||
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
||||
this.releaseRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mark module exports, i.e. to apply proper wrapping behavior on the boundaries
|
||||
@ -1726,31 +1735,31 @@ export class Program extends DiagnosticEmitter {
|
||||
}
|
||||
|
||||
/** Determines the element type of a built-in array. */
|
||||
determineBuiltinArrayType(target: Class): Type | null {
|
||||
switch (target.internalName) {
|
||||
case BuiltinSymbols.Int8Array: return Type.i8;
|
||||
case BuiltinSymbols.Uint8ClampedArray:
|
||||
case BuiltinSymbols.Uint8Array: return Type.u8;
|
||||
case BuiltinSymbols.Int16Array: return Type.i16;
|
||||
case BuiltinSymbols.Uint16Array: return Type.u16;
|
||||
case BuiltinSymbols.Int32Array: return Type.i32;
|
||||
case BuiltinSymbols.Uint32Array: return Type.u32;
|
||||
case BuiltinSymbols.Int64Array: return Type.i64;
|
||||
case BuiltinSymbols.Uint64Array: return Type.u64;
|
||||
case BuiltinSymbols.Float32Array: return Type.f32;
|
||||
case BuiltinSymbols.Float64Array: return Type.f64;
|
||||
}
|
||||
var current: Class | null = target;
|
||||
var arrayPrototype = this.arrayPrototype;
|
||||
do {
|
||||
if (current.prototype == arrayPrototype) { // Array<T>
|
||||
let typeArguments = assert(current.typeArguments);
|
||||
assert(typeArguments.length == 1);
|
||||
return typeArguments[0];
|
||||
}
|
||||
} while (current = current.base);
|
||||
return null;
|
||||
}
|
||||
// determineBuiltinArrayType(target: Class): Type | null {
|
||||
// switch (target.internalName) {
|
||||
// case BuiltinSymbols.Int8Array: return Type.i8;
|
||||
// case BuiltinSymbols.Uint8ClampedArray:
|
||||
// case BuiltinSymbols.Uint8Array: return Type.u8;
|
||||
// case BuiltinSymbols.Int16Array: return Type.i16;
|
||||
// case BuiltinSymbols.Uint16Array: return Type.u16;
|
||||
// case BuiltinSymbols.Int32Array: return Type.i32;
|
||||
// case BuiltinSymbols.Uint32Array: return Type.u32;
|
||||
// case BuiltinSymbols.Int64Array: return Type.i64;
|
||||
// case BuiltinSymbols.Uint64Array: return Type.u64;
|
||||
// case BuiltinSymbols.Float32Array: return Type.f32;
|
||||
// case BuiltinSymbols.Float64Array: return Type.f64;
|
||||
// }
|
||||
// var current: Class | null = target;
|
||||
// var arrayPrototype = this.arrayPrototype;
|
||||
// do {
|
||||
// if (current.prototype == arrayPrototype) { // Array<T>
|
||||
// let typeArguments = assert(current.typeArguments);
|
||||
// assert(typeArguments.length == 1);
|
||||
// return typeArguments[0];
|
||||
// }
|
||||
// } while (current = current.base);
|
||||
// return null;
|
||||
// }
|
||||
}
|
||||
|
||||
/** Indicates the specific kind of an {@link Element}. */
|
||||
|
@ -372,12 +372,26 @@ export class Resolver extends DiagnosticEmitter {
|
||||
);
|
||||
// recoverable
|
||||
}
|
||||
return this.resolveType(
|
||||
let type = this.resolveType(
|
||||
(<TypeDefinition>element).typeNode,
|
||||
element,
|
||||
contextualTypeArguments,
|
||||
reportMode
|
||||
);
|
||||
if (!type) return null;
|
||||
if (node.isNullable) {
|
||||
if (!type.is(TypeFlags.REFERENCE)) {
|
||||
if (reportMode == ReportMode.REPORT) {
|
||||
this.error(
|
||||
DiagnosticCode.Basic_type_0_cannot_be_nullable,
|
||||
typeNode.name.range, typeName.identifier.text
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return type.asNullable();
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
if (reportMode == ReportMode.REPORT) {
|
||||
this.error(
|
||||
@ -614,8 +628,8 @@ export class Resolver extends DiagnosticEmitter {
|
||||
case ElementKind.CLASS: { // property access on element access?
|
||||
let elementExpression = this.currentElementExpression;
|
||||
if (elementExpression) {
|
||||
let arrayType = this.program.determineBuiltinArrayType(<Class>target);
|
||||
if (!arrayType) {
|
||||
// let arrayType = this.program.determineBuiltinArrayType(<Class>target);
|
||||
// if (!arrayType) {
|
||||
let indexedGet = (<Class>target).lookupOverload(OperatorKind.INDEXED_GET);
|
||||
if (!indexedGet) {
|
||||
this.error(
|
||||
@ -624,8 +638,8 @@ export class Resolver extends DiagnosticEmitter {
|
||||
);
|
||||
return null;
|
||||
}
|
||||
arrayType = indexedGet.signature.returnType;
|
||||
}
|
||||
let arrayType = indexedGet.signature.returnType;
|
||||
// }
|
||||
if (!(target = arrayType.classReference)) {
|
||||
this.error(
|
||||
DiagnosticCode.Property_0_does_not_exist_on_type_1,
|
||||
@ -726,8 +740,8 @@ export class Resolver extends DiagnosticEmitter {
|
||||
break;
|
||||
}
|
||||
case ElementKind.CLASS: {
|
||||
let arrayType = this.program.determineBuiltinArrayType(<Class>target);
|
||||
if (!arrayType) {
|
||||
// let arrayType = this.program.determineBuiltinArrayType(<Class>target);
|
||||
// if (!arrayType) {
|
||||
let indexedGet = (<Class>target).lookupOverload(OperatorKind.INDEXED_GET);
|
||||
if (!indexedGet) {
|
||||
if (reportMode == ReportMode.REPORT) {
|
||||
@ -738,8 +752,8 @@ export class Resolver extends DiagnosticEmitter {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
arrayType = indexedGet.signature.returnType;
|
||||
}
|
||||
let arrayType = indexedGet.signature.returnType;
|
||||
// }
|
||||
if (targetExpression.kind == NodeKind.ELEMENTACCESS) { // nested element access
|
||||
if (target = arrayType.classReference) {
|
||||
this.currentThisExpression = targetExpression;
|
||||
|
@ -212,7 +212,9 @@ export class Type {
|
||||
var targetFunction: Signature | null;
|
||||
if (this.is(TypeFlags.REFERENCE)) {
|
||||
if (target.is(TypeFlags.REFERENCE)) {
|
||||
if (!this.is(TypeFlags.NULLABLE) || target.is(TypeFlags.NULLABLE)) {
|
||||
// FIXME: turned out resolveType didn't handle nullability properly, and fixing it there
|
||||
// leads to this check failing all over the place due to not yet implemented null states.
|
||||
// if (!this.is(TypeFlags.NULLABLE) || target.is(TypeFlags.NULLABLE)) {
|
||||
if (currentClass = this.classReference) {
|
||||
if (targetClass = target.classReference) {
|
||||
return currentClass.isAssignableTo(targetClass);
|
||||
@ -222,7 +224,7 @@ export class Type {
|
||||
return currentFunction.isAssignableTo(targetFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
} else if (!target.is(TypeFlags.REFERENCE)) {
|
||||
if (this.is(TypeFlags.INTEGER)) {
|
||||
|
Reference in New Issue
Block a user