Initial implementation of 'new'

This doesn't yet call the constructor or use provided parameters and just allocates raw memory
This commit is contained in:
dcodeIO
2018-01-19 16:13:14 +01:00
parent ef7a095494
commit 64c939fdc4
12 changed files with 206 additions and 50 deletions

View File

@ -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;
}

View File

@ -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 = (<FunctionPrototype>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();
}

View File

@ -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, <ExpressionRef[]>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 = <ClassPrototype>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 {

View File

@ -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 = ".";

View File

@ -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.";

View File

@ -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,

View File

@ -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;

View File

@ -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) {

View File

@ -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)
}

View File

@ -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)
)
)
)
)

15
tests/compiler/new.ts Normal file
View File

@ -0,0 +1,15 @@
class Simple {
field: i32;
}
class Generic<T> {
field: T;
}
@global
declare function allocate_memory(size: usize): usize;
export function test(): void {
var simple = new Simple();
var generic = new Generic<f64>();
}

76
tests/compiler/new.wast Normal file
View File

@ -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
;)