mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-21 10:41:42 +00:00
Avoid trampolines where optional arguments are constant literals, see #102; Fix temporary local flags not being cleared; Fix inlined temporary locals not being free'd; Fix inlined flows not breaking after returns; Allow changetype of u32s, i.e. function pointers
This commit is contained in:
@ -1945,13 +1945,6 @@ export function compileCall(
|
||||
reportNode.range, "1", typeArguments ? typeArguments.length.toString(10) : "0"
|
||||
);
|
||||
return module.createUnreachable();
|
||||
} else if (typeArguments[0].kind != TypeKind.USIZE) { // any usize
|
||||
compiler.error(
|
||||
DiagnosticCode.Operation_not_supported,
|
||||
reportNode.range
|
||||
);
|
||||
compiler.currentType = typeArguments[0];
|
||||
return module.createUnreachable();
|
||||
}
|
||||
if (operands.length != 1) {
|
||||
compiler.error(
|
||||
@ -1963,11 +1956,11 @@ export function compileCall(
|
||||
}
|
||||
arg0 = compiler.compileExpressionRetainType(
|
||||
operands[0],
|
||||
compiler.options.usizeType,
|
||||
typeArguments[0],
|
||||
WrapMode.NONE
|
||||
);
|
||||
compiler.currentType = typeArguments[0];
|
||||
if (compiler.currentType.kind != TypeKind.USIZE) {
|
||||
if (compiler.currentType.size != typeArguments[0].size) {
|
||||
compiler.error(
|
||||
DiagnosticCode.Operation_not_supported,
|
||||
reportNode.range
|
||||
|
@ -5073,10 +5073,14 @@ export class Compiler extends DiagnosticEmitter {
|
||||
|
||||
// Compile the called function's body in the scope of the inlined flow
|
||||
var bodyStatement = assert(declaration.body);
|
||||
if (bodyStatement.kind == NodeKind.BLOCK) { // it's ok to unwrap the block here
|
||||
if (bodyStatement.kind == NodeKind.BLOCK) {
|
||||
let statements = (<BlockStatement>bodyStatement).statements;
|
||||
for (let i = 0, k = statements.length; i < k; ++i) {
|
||||
body.push(this.compileStatement(statements[i]));
|
||||
let stmt = this.compileStatement(statements[i]);
|
||||
if (getExpressionId(stmt) != ExpressionId.Nop) {
|
||||
body.push(stmt);
|
||||
if (flow.isAny(FlowFlags.BREAKS | FlowFlags.CONTINUES | FlowFlags.RETURNS)) break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
body.push(this.compileStatement(bodyStatement));
|
||||
@ -5290,28 +5294,51 @@ export class Compiler extends DiagnosticEmitter {
|
||||
var returnType = instance.signature.returnType;
|
||||
var isCallImport = instance.is(CommonFlags.MODULE_IMPORT);
|
||||
|
||||
// fill up omitted arguments with zeroes
|
||||
// fill up omitted arguments with their initializers, if constant, otherwise with zeroes.
|
||||
if (numOperands < maxOperands) {
|
||||
if (!operands) {
|
||||
operands = new Array(maxOperands);
|
||||
operands.length = 0;
|
||||
}
|
||||
let parameterTypes = instance.signature.parameterTypes;
|
||||
let parameterNodes = instance.prototype.declaration.signature.parameterTypes;
|
||||
let allOptionalsAreConstant = true;
|
||||
for (let i = numArguments; i < maxArguments; ++i) {
|
||||
operands.push(parameterTypes[i].toNativeZero(module));
|
||||
let initializer = assert(parameterNodes[i].initializer);
|
||||
if (initializer.kind != NodeKind.LITERAL) {
|
||||
// TODO: other kinds might be constant as well
|
||||
allOptionalsAreConstant = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isCallImport) { // call the trampoline
|
||||
let original = instance;
|
||||
instance = this.ensureTrampoline(instance);
|
||||
if (!this.compileFunction(instance)) return module.createUnreachable();
|
||||
instance.flow.flags = original.flow.flags;
|
||||
this.program.instancesLookup.set(instance.internalName, instance); // so canOverflow can find it
|
||||
let nativeReturnType = returnType.toNativeType();
|
||||
this.currentType = returnType;
|
||||
return module.createBlock(null, [
|
||||
module.createSetGlobal(this.ensureArgcVar(), module.createI32(numArguments)),
|
||||
module.createCall(instance.internalName, operands, nativeReturnType)
|
||||
], nativeReturnType);
|
||||
if (allOptionalsAreConstant) { // inline into the call
|
||||
for (let i = numArguments; i < maxArguments; ++i) {
|
||||
operands.push(
|
||||
this.compileExpression(
|
||||
<Expression>parameterNodes[i].initializer,
|
||||
parameterTypes[i],
|
||||
ConversionKind.IMPLICIT,
|
||||
WrapMode.NONE
|
||||
)
|
||||
);
|
||||
}
|
||||
} else { // otherwise fill up with zeroes and call the trampoline
|
||||
for (let i = numArguments; i < maxArguments; ++i) {
|
||||
operands.push(parameterTypes[i].toNativeZero(module));
|
||||
}
|
||||
if (!isCallImport) {
|
||||
let original = instance;
|
||||
instance = this.ensureTrampoline(instance);
|
||||
if (!this.compileFunction(instance)) return module.createUnreachable();
|
||||
instance.flow.flags = original.flow.flags;
|
||||
this.program.instancesLookup.set(instance.internalName, instance); // so canOverflow can find it
|
||||
let nativeReturnType = returnType.toNativeType();
|
||||
this.currentType = returnType;
|
||||
return module.createBlock(null, [
|
||||
module.createSetGlobal(this.ensureArgcVar(), module.createI32(numArguments)),
|
||||
module.createCall(instance.internalName, operands, nativeReturnType)
|
||||
], nativeReturnType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6211,8 +6238,8 @@ export class Compiler extends DiagnosticEmitter {
|
||||
getExpressionType(condExprPrecomp) == NativeType.I32
|
||||
) {
|
||||
return getConstValueI32(condExprPrecomp)
|
||||
? this.compileExpression(ifThen, contextualType, ConversionKind.IMPLICIT, WrapMode.NONE)
|
||||
: this.compileExpression(ifElse, contextualType, ConversionKind.IMPLICIT, WrapMode.NONE);
|
||||
? this.compileExpressionRetainType(ifThen, contextualType, WrapMode.NONE)
|
||||
: this.compileExpressionRetainType(ifElse, contextualType, WrapMode.NONE);
|
||||
|
||||
// Otherwise recompile to the original and let the optimizer decide
|
||||
} else /* if (condExpr != condExprPrecomp) <- not guaranteed */ {
|
||||
|
@ -37,9 +37,11 @@ abstract class ExportsWalker {
|
||||
/** Program reference. */
|
||||
program: Program;
|
||||
/** Whether to include private members */
|
||||
private includePrivate: bool;
|
||||
includePrivate: bool;
|
||||
/** Elements still to do. */
|
||||
todo: Element[] = [];
|
||||
/** Already seen elements. */
|
||||
private seen: Set<Element> = new Set();
|
||||
seen: Set<Element> = new Set();
|
||||
|
||||
/** Constructs a new Element walker. */
|
||||
constructor(program: Program, includePrivate: bool = false) {
|
||||
@ -50,6 +52,8 @@ abstract class ExportsWalker {
|
||||
/** Walks all exports and calls the respective handlers. */
|
||||
walk(): void {
|
||||
for (let element of this.program.moduleLevelExports.values()) this.visitElement(element);
|
||||
var todo = this.todo;
|
||||
for (let i = 0; i < todo.length; ) this.visitElement(todo[i]);
|
||||
}
|
||||
|
||||
/** Visits an element.*/
|
||||
|
@ -239,7 +239,8 @@ export class MemorySegment {
|
||||
export class Module {
|
||||
|
||||
ref: ModuleRef;
|
||||
out: usize;
|
||||
|
||||
private cachedByValue: usize;
|
||||
|
||||
/** Maximum number of pages when targeting WASM32. */
|
||||
static readonly MAX_MEMORY_WASM32: Index = 0xffff;
|
||||
@ -250,7 +251,7 @@ export class Module {
|
||||
static create(): Module {
|
||||
var module = new Module();
|
||||
module.ref = _BinaryenModuleCreate();
|
||||
module.out = allocate_memory(16);
|
||||
module.cachedByValue = allocate_memory(16);
|
||||
return module;
|
||||
}
|
||||
|
||||
@ -259,7 +260,7 @@ export class Module {
|
||||
try {
|
||||
let module = new Module();
|
||||
module.ref = _BinaryenModuleRead(cArr, buffer.length);
|
||||
module.out = allocate_memory(3 * 8); // LLVM C-ABI, max used is 3 * usize
|
||||
module.cachedByValue = allocate_memory(3 * 8); // LLVM C-ABI, max used is 3 * usize
|
||||
return module;
|
||||
} finally {
|
||||
free_memory(changetype<usize>(cArr));
|
||||
@ -309,25 +310,25 @@ export class Module {
|
||||
// constants
|
||||
|
||||
createI32(value: i32): ExpressionRef {
|
||||
var out = this.out;
|
||||
var out = this.cachedByValue;
|
||||
_BinaryenLiteralInt32(out, value);
|
||||
return _BinaryenConst(this.ref, out);
|
||||
}
|
||||
|
||||
createI64(valueLow: i32, valueHigh: i32 = 0): ExpressionRef {
|
||||
var out = this.out;
|
||||
var out = this.cachedByValue;
|
||||
_BinaryenLiteralInt64(out, valueLow, valueHigh);
|
||||
return _BinaryenConst(this.ref, out);
|
||||
}
|
||||
|
||||
createF32(value: f32): ExpressionRef {
|
||||
var out = this.out;
|
||||
var out = this.cachedByValue;
|
||||
_BinaryenLiteralFloat32(out, value);
|
||||
return _BinaryenConst(this.ref, out);
|
||||
}
|
||||
|
||||
createF64(value: f64): ExpressionRef {
|
||||
var out = this.out;
|
||||
var out = this.cachedByValue;
|
||||
_BinaryenLiteralFloat64(out, value);
|
||||
return _BinaryenConst(this.ref, out);
|
||||
}
|
||||
@ -672,24 +673,25 @@ export class Module {
|
||||
}
|
||||
}
|
||||
|
||||
private tempName: usize = 0;
|
||||
private hasTempFunc: bool = false;
|
||||
private cachedTemporaryName: usize = 0;
|
||||
private hasTemporaryFunction: bool = false;
|
||||
|
||||
addTemporaryFunction(result: NativeType, paramTypes: NativeType[] | null, body: ExpressionRef): FunctionRef {
|
||||
this.hasTempFunc = assert(!this.hasTempFunc);
|
||||
if (!this.tempName) this.tempName = allocString(""); // works because strings are interned
|
||||
this.hasTemporaryFunction = assert(!this.hasTemporaryFunction);
|
||||
var tempName = this.cachedTemporaryName;
|
||||
if (!tempName) this.cachedTemporaryName = tempName = allocString(""); // works because strings are interned
|
||||
var cArr = allocI32Array(paramTypes);
|
||||
try {
|
||||
let typeRef = _BinaryenAddFunctionType(this.ref, this.tempName, result, cArr, paramTypes ? paramTypes.length : 0);
|
||||
return _BinaryenAddFunction(this.ref, this.tempName, typeRef, 0, 0, body);
|
||||
let typeRef = _BinaryenAddFunctionType(this.ref, tempName, result, cArr, paramTypes ? paramTypes.length : 0);
|
||||
return _BinaryenAddFunction(this.ref, tempName, typeRef, 0, 0, body);
|
||||
} finally {
|
||||
free_memory(cArr);
|
||||
}
|
||||
}
|
||||
|
||||
removeTemporaryFunction(): void {
|
||||
this.hasTempFunc = !assert(this.hasTempFunc);
|
||||
var tempName = assert(this.tempName);
|
||||
this.hasTemporaryFunction = !assert(this.hasTemporaryFunction);
|
||||
var tempName = assert(this.cachedTemporaryName);
|
||||
_BinaryenRemoveFunction(this.ref, tempName);
|
||||
_BinaryenRemoveFunctionType(this.ref, tempName);
|
||||
}
|
||||
@ -927,6 +929,19 @@ export class Module {
|
||||
}
|
||||
}
|
||||
|
||||
private cachedPrecomputeName: usize = 0;
|
||||
private cachedPrecomputeNames: usize = 0;
|
||||
|
||||
precomputeFunction(func: FunctionRef): void {
|
||||
var names = this.cachedPrecomputeNames;
|
||||
if (!names) {
|
||||
let name = allocString("precompute");
|
||||
this.cachedPrecomputeName = name;
|
||||
this.cachedPrecomputeNames = names = allocI32Array([ name ]);
|
||||
}
|
||||
_BinaryenFunctionRunPasses(func, this.ref, names, 1);
|
||||
}
|
||||
|
||||
validate(): bool {
|
||||
return _BinaryenModuleValidate(this.ref) == 1;
|
||||
}
|
||||
@ -936,7 +951,7 @@ export class Module {
|
||||
}
|
||||
|
||||
toBinary(sourceMapUrl: string | null): BinaryModule {
|
||||
var out = this.out;
|
||||
var out = this.cachedByValue;
|
||||
var cStr = allocString(sourceMapUrl);
|
||||
var binaryPtr: usize = 0;
|
||||
var sourceMapPtr: usize = 0;
|
||||
@ -965,9 +980,13 @@ export class Module {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (!this.ref) return; // sic
|
||||
assert(this.ref);
|
||||
free_memory(this.cachedByValue);
|
||||
free_memory(this.cachedTemporaryName);
|
||||
free_memory(this.cachedPrecomputeName);
|
||||
free_memory(this.cachedPrecomputeNames);
|
||||
_BinaryenModuleDispose(this.ref);
|
||||
free_memory(this.out);
|
||||
this.ref = 0;
|
||||
}
|
||||
|
||||
createRelooper(): Relooper {
|
||||
@ -1295,13 +1314,6 @@ export class Relooper {
|
||||
var relooper = new Relooper();
|
||||
relooper.module = module;
|
||||
relooper.ref = _RelooperCreate();
|
||||
return relooper;
|
||||
}
|
||||
|
||||
static createStub(module: Module): Relooper {
|
||||
var relooper = new Relooper();
|
||||
relooper.module = module;
|
||||
relooper.ref = 0;
|
||||
return relooper;
|
||||
}
|
||||
|
||||
|
@ -2923,6 +2923,7 @@ export class Function extends Element {
|
||||
if (temps && temps.length) {
|
||||
local = temps.pop();
|
||||
local.type = type;
|
||||
local.flags = CommonFlags.NONE;
|
||||
} else {
|
||||
local = this.addLocal(type);
|
||||
}
|
||||
@ -3704,6 +3705,7 @@ export class Flow {
|
||||
return existingLocal;
|
||||
}
|
||||
}
|
||||
scopedLocal.set(CommonFlags.SCOPED);
|
||||
this.scopedLocals.set(name, scopedLocal);
|
||||
if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) {
|
||||
this.setLocalWrapped(scopedLocal.index, wrapped);
|
||||
|
226
src/tokenizer.ts
226
src/tokenizer.ts
@ -167,70 +167,170 @@ export enum Token {
|
||||
}
|
||||
|
||||
export function tokenFromKeyword(text: string): Token {
|
||||
switch (text) {
|
||||
case "abstract": return Token.ABSTRACT;
|
||||
case "as": return Token.AS;
|
||||
case "async": return Token.ASYNC;
|
||||
case "await": return Token.AWAIT;
|
||||
case "break": return Token.BREAK;
|
||||
case "case": return Token.CASE;
|
||||
case "catch": return Token.CATCH;
|
||||
case "class": return Token.CLASS;
|
||||
case "continue": return Token.CONTINUE;
|
||||
case "const": return Token.CONST;
|
||||
case "constructor": return Token.CONSTRUCTOR;
|
||||
case "debugger": return Token.DEBUGGER;
|
||||
case "declare": return Token.DECLARE;
|
||||
case "default": return Token.DEFAULT;
|
||||
case "delete": return Token.DELETE;
|
||||
case "do": return Token.DO;
|
||||
case "else": return Token.ELSE;
|
||||
case "enum": return Token.ENUM;
|
||||
case "export": return Token.EXPORT;
|
||||
case "extends": return Token.EXTENDS;
|
||||
case "false": return Token.FALSE;
|
||||
case "finally": return Token.FINALLY;
|
||||
case "for": return Token.FOR;
|
||||
case "from": return Token.FROM;
|
||||
case "function": return Token.FUNCTION;
|
||||
case "get": return Token.GET;
|
||||
case "if": return Token.IF;
|
||||
case "implements": return Token.IMPLEMENTS;
|
||||
case "import": return Token.IMPORT;
|
||||
case "in": return Token.IN;
|
||||
case "instanceof": return Token.INSTANCEOF;
|
||||
case "interface": return Token.INTERFACE;
|
||||
case "is": return Token.IS;
|
||||
case "keyof": return Token.KEYOF;
|
||||
case "let": return Token.LET;
|
||||
case "module": return Token.MODULE;
|
||||
case "namespace": return Token.NAMESPACE;
|
||||
case "new": return Token.NEW;
|
||||
case "null": return Token.NULL;
|
||||
case "of": return Token.OF;
|
||||
case "package": return Token.PACKAGE;
|
||||
case "private": return Token.PRIVATE;
|
||||
case "protected": return Token.PROTECTED;
|
||||
case "public": return Token.PUBLIC;
|
||||
case "readonly": return Token.READONLY;
|
||||
case "return": return Token.RETURN;
|
||||
case "set": return Token.SET;
|
||||
case "static": return Token.STATIC;
|
||||
case "super": return Token.SUPER;
|
||||
case "switch": return Token.SWITCH;
|
||||
case "this": return Token.THIS;
|
||||
case "throw": return Token.THROW;
|
||||
case "true": return Token.TRUE;
|
||||
case "try": return Token.TRY;
|
||||
case "type": return Token.TYPE;
|
||||
case "typeof": return Token.TYPEOF;
|
||||
case "var": return Token.VAR;
|
||||
case "void": return Token.VOID;
|
||||
case "while": return Token.WHILE;
|
||||
case "with": return Token.WITH;
|
||||
case "yield": return Token.YIELD;
|
||||
default: return Token.INVALID;
|
||||
switch (text.length && text.charCodeAt(0)) {
|
||||
case CharCode.a: {
|
||||
switch (text) {
|
||||
case "abstract": return Token.ABSTRACT;
|
||||
case "as": return Token.AS;
|
||||
case "async": return Token.ASYNC;
|
||||
case "await": return Token.AWAIT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.b: {
|
||||
switch (text) {
|
||||
case "break": return Token.BREAK;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.c: {
|
||||
switch (text) {
|
||||
case "case": return Token.CASE;
|
||||
case "catch": return Token.CATCH;
|
||||
case "class": return Token.CLASS;
|
||||
case "continue": return Token.CONTINUE;
|
||||
case "const": return Token.CONST;
|
||||
case "constructor": return Token.CONSTRUCTOR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.d: {
|
||||
switch (text) {
|
||||
case "debugger": return Token.DEBUGGER;
|
||||
case "declare": return Token.DECLARE;
|
||||
case "default": return Token.DEFAULT;
|
||||
case "delete": return Token.DELETE;
|
||||
case "do": return Token.DO;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.e: {
|
||||
switch (text) {
|
||||
case "else": return Token.ELSE;
|
||||
case "enum": return Token.ENUM;
|
||||
case "export": return Token.EXPORT;
|
||||
case "extends": return Token.EXTENDS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.f: {
|
||||
switch (text) {
|
||||
case "false": return Token.FALSE;
|
||||
case "finally": return Token.FINALLY;
|
||||
case "for": return Token.FOR;
|
||||
case "from": return Token.FROM;
|
||||
case "function": return Token.FUNCTION;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.g: {
|
||||
switch (text) {
|
||||
case "get": return Token.GET;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.i: {
|
||||
switch (text) {
|
||||
case "if": return Token.IF;
|
||||
case "implements": return Token.IMPLEMENTS;
|
||||
case "import": return Token.IMPORT;
|
||||
case "in": return Token.IN;
|
||||
case "instanceof": return Token.INSTANCEOF;
|
||||
case "interface": return Token.INTERFACE;
|
||||
case "is": return Token.IS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.k: {
|
||||
switch (text) {
|
||||
case "keyof": return Token.KEYOF;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.l: {
|
||||
switch (text) {
|
||||
case "let": return Token.LET;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.m: {
|
||||
switch (text) {
|
||||
case "module": return Token.MODULE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.n: {
|
||||
switch (text) {
|
||||
case "namespace": return Token.NAMESPACE;
|
||||
case "new": return Token.NEW;
|
||||
case "null": return Token.NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.o: {
|
||||
switch (text) {
|
||||
case "of": return Token.OF;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.p: {
|
||||
switch (text) {
|
||||
case "package": return Token.PACKAGE;
|
||||
case "private": return Token.PRIVATE;
|
||||
case "protected": return Token.PROTECTED;
|
||||
case "public": return Token.PUBLIC;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.r: {
|
||||
switch (text) {
|
||||
case "readonly": return Token.READONLY;
|
||||
case "return": return Token.RETURN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.s: {
|
||||
switch (text) {
|
||||
case "set": return Token.SET;
|
||||
case "static": return Token.STATIC;
|
||||
case "super": return Token.SUPER;
|
||||
case "switch": return Token.SWITCH;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.t: {
|
||||
switch (text) {
|
||||
case "this": return Token.THIS;
|
||||
case "throw": return Token.THROW;
|
||||
case "true": return Token.TRUE;
|
||||
case "try": return Token.TRY;
|
||||
case "type": return Token.TYPE;
|
||||
case "typeof": return Token.TYPEOF;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.v: {
|
||||
switch (text) {
|
||||
case "var": return Token.VAR;
|
||||
case "void": return Token.VOID;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.w: {
|
||||
switch (text) {
|
||||
case "while": return Token.WHILE;
|
||||
case "with": return Token.WITH;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharCode.y: {
|
||||
switch (text) {
|
||||
case "yield": return Token.YIELD;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Token.INVALID;
|
||||
}
|
||||
|
||||
export function tokenIsAlsoIdentifier(token: Token): bool {
|
||||
|
Reference in New Issue
Block a user