diff --git a/src/ast.ts b/src/ast.ts index 0cbde578..d7f3b0fe 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -2,7 +2,7 @@ import { PATH_DELIMITER, STATIC_DELIMITER, INSTANCE_DELIMITER -} from "./constants"; +} from "./program"; import { Token, @@ -1508,16 +1508,6 @@ export function hasDecorator(name: string, decorators: Decorator[] | null): bool return getFirstDecorator(name, decorators) != null; } -/** Mangles a path to an internal path. */ -export function mangleInternalPath(path: string): string { - // not necessary with current config - // if (PATH_DELIMITER.charCodeAt(0) != CharCode.SLASH) - // path = path.replace("/", PATH_DELIMITER); - // if (PARENT_SUBST != "..") - // path = path.replace("..", PARENT_SUBST); - return path; -} - /** Mangles a declaration's name to an internal name. */ export function mangleInternalName(declaration: DeclarationStatement, asGlobal: bool = false): string { var name = declaration.name.name; @@ -1535,3 +1525,13 @@ export function mangleInternalName(declaration: DeclarationStatement, asGlobal: return name; return declaration.range.source.internalPath + PATH_DELIMITER + name; } + +/** Mangles an external to an internal path. */ +export function mangleInternalPath(path: string): string { + // not necessary with current config + // if (PATH_DELIMITER.charCodeAt(0) != CharCode.SLASH) + // path = path.replace("/", PATH_DELIMITER); + // if (PARENT_SUBST != "..") + // path = path.replace("..", PARENT_SUBST); + return path; +} diff --git a/src/builtins.ts b/src/builtins.ts index ea69524e..dce7b473 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -37,7 +37,8 @@ import { Global, FunctionPrototype, Local, - ElementFlags + ElementFlags, + Class } from "./program"; /** Initializes the specified program with built-in constants and functions. */ @@ -1824,3 +1825,26 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty compiler.error(DiagnosticCode.Operation_not_supported, reportNode.range); return module.createUnreachable(); } + +/** Compiles an allocation of the specified class. */ +export function compileAllocate(compiler: Compiler, cls: Class, reportNode: Node): ExpressionRef { + var program = cls.program; + var prototype = program.elements.get(compiler.options.allocateImpl); + if (prototype) { + var instance = (prototype).resolve(); // reports + if (instance) { + var usizeType = program.target == Target.WASM64 ? Type.usize64 : Type.usize32; + if (!instance.is(ElementFlags.GENERIC) && instance.returnType == usizeType && instance.parameters.length == 1 && instance.parameters[0].type == usizeType) { + if (compiler.compileFunction(instance)) // reports + return compiler.makeCall(instance, [ + program.target == Target.WASM64 + ? compiler.module.createI64(cls.currentMemoryOffset) + : compiler.module.createI32(cls.currentMemoryOffset) + ]); + } else + program.error(DiagnosticCode.Implementation_0_must_match_the_signature_1, reportNode.range, compiler.options.allocateImpl, "(size: usize): usize"); + } + } else + program.error(DiagnosticCode.Cannot_find_name_0, reportNode.range, compiler.options.allocateImpl); + return compiler.module.createUnreachable(); +} diff --git a/src/compiler.ts b/src/compiler.ts index fe64db0f..0aa33961 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1,12 +1,9 @@ import { compileCall as compileBuiltinCall, - compileGetConstant as compileBuiltinGetConstant + compileGetConstant as compileBuiltinGetConstant, + compileAllocate as compileBuiltinAllocate } from "./builtins"; -import { - PATH_DELIMITER -} from "./constants"; - import { DiagnosticCode, DiagnosticEmitter @@ -45,7 +42,8 @@ import { VariableLikeElement, Flow, FlowFlags, - ElementFlags + ElementFlags, + PATH_DELIMITER } from "./program"; import { @@ -146,6 +144,10 @@ export class Options { noAssert: bool = false; /** If true, does not set up a memory. */ noMemory: bool = false; + /** Memory allocation implementation to use. */ + allocateImpl: string = "allocate_memory"; + /** Memory freeing implementation to use. */ + freeImpl: string = "free_memory"; } /** Indicates the desired kind of a conversion. */ @@ -545,15 +547,15 @@ export class Compiler extends DiagnosticEmitter { return true; var declaration = instance.prototype.declaration; - if (!declaration) - throw new Error("declaration expected"); // built-ins are not compiled here if (instance.is(ElementFlags.DECLARED)) { - if (declaration.statements) { + if (declaration && declaration.statements) { this.error(DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts, declaration.name.range); return false; } } else { + if (!declaration) + throw new Error("declaration expected"); // built-ins are not compiled here if (!declaration.statements) { this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, declaration.name.range); return false; @@ -566,6 +568,7 @@ export class Compiler extends DiagnosticEmitter { // compile statements var stmts: ExpressionRef[] | null = null; if (!instance.is(ElementFlags.DECLARED)) { + declaration = assert(declaration); var previousFunction = this.currentFunction; this.currentFunction = instance; var statements = assert(declaration.statements); @@ -601,12 +604,12 @@ export class Compiler extends DiagnosticEmitter { // create the function if (instance.is(ElementFlags.DECLARED)) { - this.module.addFunctionImport(instance.internalName, instance.prototype.namespace ? instance.prototype.namespace.simpleName : "env", declaration.name.name, typeRef); + this.module.addFunctionImport(instance.internalName, instance.prototype.namespace ? instance.prototype.namespace.simpleName : "env", instance.simpleName, typeRef); } else { this.module.addFunction(instance.internalName, typeRef, typesToNativeTypes(instance.additionalLocals), this.module.createBlock(null, stmts, NativeType.None)); } instance.finalize(); - if (declaration.range.source.isEntry && declaration.isTopLevelExport) { + if (declaration && declaration.range.source.isEntry && declaration.isTopLevelExport) { this.module.addFunctionExport(instance.internalName, declaration.name.name); } return true; @@ -2637,7 +2640,8 @@ export class Compiler extends DiagnosticEmitter { return this.makeCall(functionInstance, operands); } - private makeCall(functionInstance: Function, operands: ExpressionRef[] | null = null): ExpressionRef { + /** Makes a call operation as is. */ + makeCall(functionInstance: Function, operands: ExpressionRef[] | null = null): ExpressionRef { if (!(functionInstance.is(ElementFlags.COMPILED) || this.compileFunction(functionInstance))) return this.module.createUnreachable(); @@ -2791,7 +2795,20 @@ export class Compiler extends DiagnosticEmitter { } compileNewExpression(expression: NewExpression, contextualType: Type): ExpressionRef { - throw new Error("not implemented"); + var resolved = this.program.resolveExpression(expression.expression, this.currentFunction); // reports + if (resolved) { + if (resolved.element.kind == ElementKind.CLASS_PROTOTYPE) { + var prototype = resolved.element; + var instance = prototype.resolveInclTypeArguments(expression.typeArguments, null, expression); // reports + if (instance) { + // TODO: call constructor + this.currentType = instance.type; + return compileBuiltinAllocate(this, instance, expression); + } + } else + this.error(DiagnosticCode.Cannot_use_new_with_an_expression_whose_type_lacks_a_construct_signature, expression.expression.range); + } + return this.module.createUnreachable(); } compileParenthesizedExpression(expression: ParenthesizedExpression, contextualType: Type): ExpressionRef { diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index c5a989d1..00000000 --- a/src/constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -// internal naming scheme - -/** Path delimited inserted between file system levels. */ -export const PATH_DELIMITER = "/"; -/** Substitution used to indicate the parent directory. */ -export const PARENT_SUBST = ".."; -/** Function name prefix used for getters. */ -export const GETTER_PREFIX = "get:"; -/** Function name prefix used for setters. */ -export const SETTER_PREFIX = "set:"; -/** Delimiter used between class names and instance members. */ -export const INSTANCE_DELIMITER = "#"; -/** Delimiter used between class and namespace names and static members. */ -export const STATIC_DELIMITER = "."; diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 92a8ddcc..e99857f5 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -12,6 +12,7 @@ export enum DiagnosticCode { Structs_cannot_implement_interfaces = 108, Invalid_regular_expression_flags = 109, Type_0_cannot_be_reinterpreted_as_type_1 = 110, + Implementation_0_must_match_the_signature_1 = 111, Unterminated_string_literal = 1002, Identifier_expected = 1003, _0_expected = 1005, @@ -70,6 +71,7 @@ export enum DiagnosticCode { _super_can_only_be_referenced_in_a_derived_class = 2335, Property_0_does_not_exist_on_type_1 = 2339, Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures = 2349, + Cannot_use_new_with_an_expression_whose_type_lacks_a_construct_signature = 2351, A_function_whose_declared_type_is_not_void_must_return_a_value = 2355, The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access = 2357, The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access = 2364, @@ -101,6 +103,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 108: return "Structs cannot implement interfaces."; case 109: return "Invalid regular expression flags."; case 110: return "Type '{0}' cannot be reinterpreted as type '{1}'."; + case 111: return "Implementation '{0}' must match the signature '{1}'."; case 1002: return "Unterminated string literal."; case 1003: return "Identifier expected."; case 1005: return "'{0}' expected."; @@ -159,6 +162,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2335: return "'super' can only be referenced in a derived class."; case 2339: return "Property '{0}' does not exist on type '{1}'."; case 2349: return "Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures."; + case 2351: return "Cannot use 'new' with an expression whose type lacks a construct signature."; case 2355: return "A function whose declared type is not 'void' must return a value."; case 2357: return "The operand of an increment or decrement operator must be a variable or a property access."; case 2364: return "The left-hand side of an assignment expression must be a variable or a property access."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 8df1f6ea..7591fe0d 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -10,6 +10,7 @@ "Structs cannot implement interfaces.": 108, "Invalid regular expression flags.": 109, "Type '{0}' cannot be reinterpreted as type '{1}'.": 110, + "Implementation '{0}' must match the signature '{1}'.": 111, "Unterminated string literal.": 1002, "Identifier expected.": 1003, @@ -70,6 +71,7 @@ "'super' can only be referenced in a derived class.": 2335, "Property '{0}' does not exist on type '{1}'.": 2339, "Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures.": 2349, + "Cannot use 'new' with an expression whose type lacks a construct signature.": 2351, "A function whose declared type is not 'void' must return a value.": 2355, "The operand of an increment or decrement operator must be a variable or a property access.": 2357, "The left-hand side of an assignment expression must be a variable or a property access.": 2364, diff --git a/src/program.ts b/src/program.ts index 8a260692..406fccf1 100644 --- a/src/program.ts +++ b/src/program.ts @@ -6,14 +6,6 @@ import { Target } from "./compiler"; -import { - PATH_DELIMITER, - GETTER_PREFIX, - SETTER_PREFIX, - STATIC_DELIMITER, - INSTANCE_DELIMITER -} from "./constants"; - import { DiagnosticCode, DiagnosticMessage, @@ -81,6 +73,19 @@ import { NativeType } from "./module"; +/** Path delimiter inserted between file system levels. */ +export const PATH_DELIMITER = "/"; +/** Substitution used to indicate the parent directory. */ +export const PARENT_SUBST = ".."; +/** Function name prefix used for getters. */ +export const GETTER_PREFIX = "get:"; +/** Function name prefix used for setters. */ +export const SETTER_PREFIX = "set:"; +/** Delimiter used between class names and instance members. */ +export const INSTANCE_DELIMITER = "#"; +/** Delimiter used between class and namespace names and static members. */ +export const STATIC_DELIMITER = "."; + class QueuedExport { isReExport: bool; referencedName: string; diff --git a/tests/compiler.js b/tests/compiler.js index c9f0fb84..c781893b 100644 --- a/tests/compiler.js +++ b/tests/compiler.js @@ -84,7 +84,14 @@ glob.sync(filter, { cwd: __dirname + "/compiler" }).forEach(filename => { externalFunc: function(arg0, arg1, arg2) { console.log("env.externalFunc called with: " + arg0 + ", " + arg1 + ", " + arg2); }, - externalConst: 1 + externalConst: 1, + allocate_memory: function(size) { + console.log("env.allocate_memory called with: " + size); + return 0; + }, + free_memory: function(ptr) { + console.log("env.free_memory called with: " + ptr); + } }, external: { externalFunc: function(arg0, arg1, arg2) { diff --git a/tests/compiler/enum.ts b/tests/compiler/enum.ts index af6472de..58574179 100644 --- a/tests/compiler/enum.ts +++ b/tests/compiler/enum.ts @@ -24,6 +24,6 @@ function getZero(): i32 { } export enum NonConstant { - ZERO = getZero(), - ONE + ZERO = getZero(), // cannot export a mutable global + ONE // cannot export a mutable global (tsc doesn't allow this) } diff --git a/tests/compiler/new.optimized.wast b/tests/compiler/new.optimized.wast new file mode 100644 index 00000000..8c4f7716 --- /dev/null +++ b/tests/compiler/new.optimized.wast @@ -0,0 +1,20 @@ +(module + (type $ii (func (param i32) (result i32))) + (type $v (func)) + (import "env" "allocate_memory" (func $new/allocate_memory (param i32) (result i32))) + (memory $0 1) + (export "test" (func $new/test)) + (export "memory" (memory $0)) + (func $new/test (; 1 ;) (type $v) + (drop + (call $new/allocate_memory + (i32.const 4) + ) + ) + (drop + (call $new/allocate_memory + (i32.const 8) + ) + ) + ) +) diff --git a/tests/compiler/new.ts b/tests/compiler/new.ts new file mode 100644 index 00000000..6c329c81 --- /dev/null +++ b/tests/compiler/new.ts @@ -0,0 +1,15 @@ +class Simple { + field: i32; +} + +class Generic { + field: T; +} + +@global +declare function allocate_memory(size: usize): usize; + +export function test(): void { + var simple = new Simple(); + var generic = new Generic(); +} diff --git a/tests/compiler/new.wast b/tests/compiler/new.wast new file mode 100644 index 00000000..0874ea8f --- /dev/null +++ b/tests/compiler/new.wast @@ -0,0 +1,76 @@ +(module + (type $ii (func (param i32) (result i32))) + (type $v (func)) + (import "env" "allocate_memory" (func $new/allocate_memory (param i32) (result i32))) + (global $HEAP_BASE i32 (i32.const 4)) + (memory $0 1) + (export "test" (func $new/test)) + (export "memory" (memory $0)) + (func $new/test (; 1 ;) (type $v) + (local $0 i32) + (local $1 i32) + (set_local $0 + (call $new/allocate_memory + (i32.const 4) + ) + ) + (set_local $1 + (call $new/allocate_memory + (i32.const 8) + ) + ) + ) +) +(; +[program.elements] + GLOBAL: NaN + GLOBAL: Infinity + FUNCTION_PROTOTYPE: isNaN + FUNCTION_PROTOTYPE: isFinite + FUNCTION_PROTOTYPE: clz + FUNCTION_PROTOTYPE: ctz + FUNCTION_PROTOTYPE: popcnt + FUNCTION_PROTOTYPE: rotl + FUNCTION_PROTOTYPE: rotr + FUNCTION_PROTOTYPE: abs + FUNCTION_PROTOTYPE: max + FUNCTION_PROTOTYPE: min + FUNCTION_PROTOTYPE: ceil + FUNCTION_PROTOTYPE: floor + FUNCTION_PROTOTYPE: copysign + FUNCTION_PROTOTYPE: nearest + FUNCTION_PROTOTYPE: reinterpret + FUNCTION_PROTOTYPE: sqrt + FUNCTION_PROTOTYPE: trunc + FUNCTION_PROTOTYPE: load + FUNCTION_PROTOTYPE: store + FUNCTION_PROTOTYPE: sizeof + FUNCTION_PROTOTYPE: select + FUNCTION_PROTOTYPE: unreachable + FUNCTION_PROTOTYPE: current_memory + FUNCTION_PROTOTYPE: grow_memory + FUNCTION_PROTOTYPE: changetype + FUNCTION_PROTOTYPE: assert + FUNCTION_PROTOTYPE: i8 + FUNCTION_PROTOTYPE: i16 + FUNCTION_PROTOTYPE: i32 + FUNCTION_PROTOTYPE: i64 + FUNCTION_PROTOTYPE: u8 + FUNCTION_PROTOTYPE: u16 + FUNCTION_PROTOTYPE: u32 + FUNCTION_PROTOTYPE: u64 + FUNCTION_PROTOTYPE: bool + FUNCTION_PROTOTYPE: f32 + FUNCTION_PROTOTYPE: f64 + FUNCTION_PROTOTYPE: isize + FUNCTION_PROTOTYPE: usize + GLOBAL: HEAP_BASE + CLASS_PROTOTYPE: new/Simple + CLASS_PROTOTYPE: new/Generic + FUNCTION_PROTOTYPE: new/allocate_memory + FUNCTION_PROTOTYPE: allocate_memory + FUNCTION_PROTOTYPE: new/test +[program.exports] + FUNCTION_PROTOTYPE: allocate_memory + FUNCTION_PROTOTYPE: new/test +;)