diff --git a/src/builtins.ts b/src/builtins.ts index 3a3da135..4fd313b7 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -13,7 +13,8 @@ import { import { Node, Expression, - BinaryExpression + BinaryExpression, + SourceKind } from "./ast"; import { @@ -1539,6 +1540,8 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty compiler.error(DiagnosticCode.Operation_not_supported, reportNode.range); return module.createUnreachable(); } + if (reportNode.range.source.sourceKind != SourceKind.STDLIB) + compiler.warning(DiagnosticCode.Operation_is_unsafe, reportNode.range); return arg0; // any usize to any usize case "assert": // assert(isTrueish: T, message?: string) -> T with T != null (see also assembly.d.ts) diff --git a/src/compiler.ts b/src/compiler.ts index 7eabc43a..c7be808a 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -102,6 +102,7 @@ import { ParenthesizedExpression, PropertyAccessExpression, TernaryExpression, + ArrayLiteralExpression, StringLiteralExpression, UnaryPostfixExpression, UnaryPrefixExpression, @@ -2835,8 +2836,13 @@ export class Compiler extends DiagnosticEmitter { compileLiteralExpression(expression: LiteralExpression, contextualType: Type): ExpressionRef { switch (expression.literalKind) { - // case LiteralKind.ARRAY: - // return this.compileStaticArray(...); + + case LiteralKind.ARRAY: + var classType = contextualType.classType; + if (classType && classType == this.program.elements.get("Array") && classType.typeArguments && classType.typeArguments.length == 1) + return this.compileStaticArray(classType.typeArguments[0], (expression).elementExpressions); + this.error(DiagnosticCode.Operation_not_supported, expression.range); + return this.module.createUnreachable(); case LiteralKind.FLOAT: { var floatValue = (expression).value; @@ -2904,7 +2910,19 @@ export class Compiler extends DiagnosticEmitter { : this.module.createI32(stringOffset.lo); } - compileStaticArray(elementType: Type, expressions: Expression): ExpressionRef { + compileStaticArray(elementType: Type, expressions: (Expression | null)[]): ExpressionRef { + // compile as static if all element expressions are precomputable, otherwise + // initialize in place. + var exprs = new Array(expressions.length); + var isStatic = true; + var expr: BinaryenExpressionRef; + for (var i = 0; i < expressions.length; ++i) { + exprs[i] = expressions[i] ? this.compileExpression(expressions[i], elementType) : elementType.toNativeZero(this.module); + if (isStatic && _BinaryenExpressionGetId(expr = this.precomputeExpressionRef(exprs[i])) == ExpressionId.Const) { + // TODO: use multiple arrays of possible native types? + } else + isStatic = false; + } throw new Error("not implemented"); } diff --git a/std/assembly/string.ts b/std/assembly/string.ts index 1c3a93a9..11869272 100644 --- a/std/assembly/string.ts +++ b/std/assembly/string.ts @@ -325,6 +325,7 @@ function isWhiteSpaceOrLineTerminator(c: u16): bool { const enum CharCode { PLUS = 0x2B, MINUS = 0x2D, + DOT = 0x2E, _0 = 0x30, _1 = 0x31, _2 = 0x32, @@ -337,11 +338,13 @@ const enum CharCode { _9 = 0x39, A = 0x41, B = 0x42, + E = 0x45, O = 0x4F, X = 0x58, Z = 0x5a, a = 0x61, b = 0x62, + e = 0x65, o = 0x6F, x = 0x78, z = 0x7A @@ -410,16 +413,62 @@ export function parseInt(str: String, radix: i32 = 0): i64 { else if (code >= CharCode.a && code <= CharCode.z) code -= CharCode.a - 10; else - return sign * num; + break; if (code >= radix) - return sign * num; + break; num = (num * radix) + code; ptr += 2; } return sign * num; } -// @binding(CALL, [ STRING ], PASS_THRU) export function parseFloat(str: string): f64 { - throw new Error("not implemented"); + var len: i32 = str.length; + if (!len) + return NaN; + var ptr = changetype(str) /* + HEAD -> offset */; + var code = load(ptr, HEAD); + + // determine sign + var sign: f64; + if (code == CharCode.MINUS) { + if (!--len) + return NaN; + code = load(ptr += 2, HEAD); + sign = -1; + } else if (code == CharCode.PLUS) { + if (!--len) + return NaN; + code = load(ptr += 2, HEAD); + sign = 1; + } else + sign = 1; + + // calculate value + var num: f64 = 0; + while (len--) { + code = load(ptr, HEAD); + if (code == CharCode.DOT) { + ptr += 2; + var fac: f64 = 0.1; // precision :( + while (len--) { + code = load(ptr, HEAD); + if (code == CharCode.E || code == CharCode.e) + assert(false); // TODO + code -= CharCode._0; + if (code > 9) + break; + num += code * fac; + fac *= 0.1; + ptr += 2; + } + break; + } + code -= CharCode._0; + if (code >= 10) + break; + num = (num * 10) + code; + ptr += 2; + } + return sign * num; } diff --git a/tests/compiler.js b/tests/compiler.js index 6f279d9f..8d814439 100644 --- a/tests/compiler.js +++ b/tests/compiler.js @@ -78,8 +78,8 @@ glob.sync(filter, { cwd: __dirname + "/compiler" }).forEach(filename => { // already covered by instantiate below, which is also able to use imports, but doesn't // provide as much debugging information. might be necessary to remove this one once imports // are tested more. - module.interpret(); - console.log(chalk.green("interpret OK")); + // module.interpret(); + // console.log(chalk.green("interpret OK")); try { var binary = module.toBinary(); var wasmModule = new WebAssembly.Module(binary); diff --git a/tests/compiler/std/string.optimized.wast b/tests/compiler/std/string.optimized.wast index 4e67231a..d83dbaed 100644 --- a/tests/compiler/std/string.optimized.wast +++ b/tests/compiler/std/string.optimized.wast @@ -3,6 +3,7 @@ (type $iii (func (param i32 i32) (result i32))) (type $iiii (func (param i32 i32 i32) (result i32))) (type $iiI (func (param i32 i32) (result i64))) + (type $iF (func (param i32) (result f64))) (type $v (func)) (global $std/string/str (mut i32) (i32.const 8)) (memory $0 1) @@ -21,6 +22,9 @@ (data (i32.const 184) "\05\00\00\000\00x\00F\000\00F") (data (i32.const 200) "\03\00\00\000\001\001") (data (i32.const 216) "\04\00\00\000\00x\001\00g") + (data (i32.const 232) "\03\00\00\000\00.\001") + (data (i32.const 248) "\03\00\00\00.\002\005") + (data (i32.const 264) "\08\00\00\00.\001\00f\00o\00o\00b\00a\00r") (export "getString" (func $std/string/getString)) (export "memory" (memory $0)) (start $start) @@ -607,62 +611,39 @@ ) ) ) - (loop $continue|1 - (if - (block (result i32) - (set_local $3 - (i32.sub - (tee_local $2 - (get_local $3) - ) - (i32.const 1) - ) - ) - (get_local $2) - ) - (block - (if - (i32.and - (select - (i32.le_s - (tee_local $2 - (i32.load16_u offset=4 - (get_local $0) - ) - ) - (i32.const 57) - ) - (i32.ge_s - (get_local $2) - (i32.const 48) - ) - (i32.ge_s - (get_local $2) - (i32.const 48) - ) - ) - (i32.const 1) - ) - (set_local $2 + (block $break|1 + (loop $continue|1 + (if + (block (result i32) + (set_local $3 (i32.sub - (get_local $2) - (i32.const 48) + (tee_local $2 + (get_local $3) + ) + (i32.const 1) ) ) + (get_local $2) + ) + (block (if (i32.and (select (i32.le_s - (get_local $2) - (i32.const 90) + (tee_local $2 + (i32.load16_u offset=4 + (get_local $0) + ) + ) + (i32.const 57) ) (i32.ge_s (get_local $2) - (i32.const 65) + (i32.const 48) ) (i32.ge_s (get_local $2) - (i32.const 65) + (i32.const 48) ) ) (i32.const 1) @@ -670,7 +651,7 @@ (set_local $2 (i32.sub (get_local $2) - (i32.const 55) + (i32.const 48) ) ) (if @@ -678,15 +659,15 @@ (select (i32.le_s (get_local $2) - (i32.const 122) + (i32.const 90) ) (i32.ge_s (get_local $2) - (i32.const 97) + (i32.const 65) ) (i32.ge_s (get_local $2) - (i32.const 97) + (i32.const 65) ) ) (i32.const 1) @@ -694,50 +675,64 @@ (set_local $2 (i32.sub (get_local $2) - (i32.const 87) + (i32.const 55) ) ) - (return - (i64.mul - (get_local $5) - (get_local $4) + (if + (i32.and + (select + (i32.le_s + (get_local $2) + (i32.const 122) + ) + (i32.ge_s + (get_local $2) + (i32.const 97) + ) + (i32.ge_s + (get_local $2) + (i32.const 97) + ) + ) + (i32.const 1) ) + (set_local $2 + (i32.sub + (get_local $2) + (i32.const 87) + ) + ) + (br $break|1) ) ) ) - ) - (if - (i32.ge_s - (get_local $2) - (get_local $1) - ) - (return - (i64.mul - (get_local $5) - (get_local $4) - ) - ) - ) - (set_local $4 - (i64.add - (i64.mul - (get_local $4) - (i64.extend_s/i32 - (get_local $1) - ) - ) - (i64.extend_s/i32 + (br_if $break|1 + (i32.ge_s (get_local $2) + (get_local $1) ) ) - ) - (set_local $0 - (i32.add - (get_local $0) - (i32.const 2) + (set_local $4 + (i64.add + (i64.mul + (get_local $4) + (i64.extend_s/i32 + (get_local $1) + ) + ) + (i64.extend_s/i32 + (get_local $2) + ) + ) ) + (set_local $0 + (i32.add + (get_local $0) + (i32.const 2) + ) + ) + (br $continue|1) ) - (br $continue|1) ) ) ) @@ -746,7 +741,251 @@ (get_local $4) ) ) - (func $start (; 7 ;) (type $v) + (func $std:string/parseFloat (; 7 ;) (type $iF) (param $0 i32) (result f64) + (local $1 i32) + (local $2 i32) + (local $3 f64) + (local $4 f64) + (local $5 f64) + (if + (i32.eqz + (tee_local $2 + (i32.load + (get_local $0) + ) + ) + ) + (return + (f64.const nan:0x8000000000000) + ) + ) + (set_local $5 + (if (result f64) + (i32.eq + (tee_local $0 + (i32.load16_u offset=4 + (tee_local $1 + (get_local $0) + ) + ) + ) + (i32.const 45) + ) + (block (result f64) + (if + (i32.eqz + (tee_local $2 + (i32.sub + (get_local $2) + (i32.const 1) + ) + ) + ) + (return + (f64.const nan:0x8000000000000) + ) + ) + (drop + (i32.load16_u offset=4 + (tee_local $1 + (i32.add + (get_local $1) + (i32.const 2) + ) + ) + ) + ) + (f64.const -1) + ) + (if (result f64) + (i32.eq + (get_local $0) + (i32.const 43) + ) + (block (result f64) + (if + (i32.eqz + (tee_local $2 + (i32.sub + (get_local $2) + (i32.const 1) + ) + ) + ) + (return + (f64.const nan:0x8000000000000) + ) + ) + (drop + (i32.load16_u offset=4 + (tee_local $1 + (i32.add + (get_local $1) + (i32.const 2) + ) + ) + ) + ) + (f64.const 1) + ) + (f64.const 1) + ) + ) + ) + (block $break|0 + (loop $continue|0 + (if + (block (result i32) + (set_local $2 + (i32.sub + (tee_local $0 + (get_local $2) + ) + (i32.const 1) + ) + ) + (get_local $0) + ) + (block + (if + (i32.eq + (tee_local $0 + (i32.load16_u offset=4 + (get_local $1) + ) + ) + (i32.const 46) + ) + (block + (set_local $1 + (i32.add + (get_local $1) + (i32.const 2) + ) + ) + (set_local $4 + (f64.const 0.1) + ) + (block $break|1 + (loop $continue|1 + (if + (block (result i32) + (set_local $2 + (i32.sub + (tee_local $0 + (get_local $2) + ) + (i32.const 1) + ) + ) + (get_local $0) + ) + (block + (if + (i32.and + (select + (i32.eq + (tee_local $0 + (i32.load16_u offset=4 + (get_local $1) + ) + ) + (i32.const 69) + ) + (i32.eq + (get_local $0) + (i32.const 101) + ) + (i32.eq + (get_local $0) + (i32.const 69) + ) + ) + (i32.const 1) + ) + (unreachable) + ) + (br_if $break|1 + (i32.gt_u + (tee_local $0 + (i32.sub + (get_local $0) + (i32.const 48) + ) + ) + (i32.const 9) + ) + ) + (set_local $3 + (f64.add + (get_local $3) + (f64.mul + (f64.convert_s/i32 + (get_local $0) + ) + (get_local $4) + ) + ) + ) + (set_local $4 + (f64.mul + (get_local $4) + (f64.const 0.1) + ) + ) + (set_local $1 + (i32.add + (get_local $1) + (i32.const 2) + ) + ) + (br $continue|1) + ) + ) + ) + ) + (br $break|0) + ) + ) + (br_if $break|0 + (i32.ge_u + (tee_local $0 + (i32.sub + (get_local $0) + (i32.const 48) + ) + ) + (i32.const 10) + ) + ) + (set_local $3 + (f64.add + (f64.mul + (get_local $3) + (f64.const 10) + ) + (f64.convert_s/i32 + (get_local $0) + ) + ) + ) + (set_local $1 + (i32.add + (get_local $1) + (i32.const 2) + ) + ) + (br $continue|0) + ) + ) + ) + ) + (f64.mul + (get_local $5) + (get_local $3) + ) + ) + (func $start (; 8 ;) (type $v) (if (i32.ne (get_global $std/string/str) @@ -908,5 +1147,50 @@ ) (unreachable) ) + (if + (f64.ne + (call $std:string/parseFloat + (i32.const 120) + ) + (f64.const 0) + ) + (unreachable) + ) + (if + (f64.ne + (call $std:string/parseFloat + (i32.const 128) + ) + (f64.const 1) + ) + (unreachable) + ) + (if + (f64.ne + (call $std:string/parseFloat + (i32.const 232) + ) + (f64.const 0.1) + ) + (unreachable) + ) + (if + (f64.ne + (call $std:string/parseFloat + (i32.const 248) + ) + (f64.const 0.25) + ) + (unreachable) + ) + (if + (f64.ne + (call $std:string/parseFloat + (i32.const 264) + ) + (f64.const 0.1) + ) + (unreachable) + ) ) ) diff --git a/tests/compiler/std/string.ts b/tests/compiler/std/string.ts index 69692f37..8ec5f080 100644 --- a/tests/compiler/std/string.ts +++ b/tests/compiler/std/string.ts @@ -3,7 +3,7 @@ var str: string = "hi, I'm a string"; // exactly once in static memory -assert(changetype(str) === changetype("hi, I'm a string")); +assert(changetype(str) == changetype("hi, I'm a string")); assert(str.length == 16); assert(str.charCodeAt(0) == 0x68); @@ -25,3 +25,9 @@ assert(parseInt("0xf0f") == 0xf0f); assert(parseInt("0xF0F") == 0xf0f); assert(parseInt("011") == 11); // not octal assert(parseInt("0x1g") == 1); // not valid + +assert(parseFloat("0") == 0); +assert(parseFloat("1") == 1); +assert(parseFloat("0.1") == 0.1); +assert(parseFloat(".25") == 0.25); +assert(parseFloat(".1foobar") == 0.1); diff --git a/tests/compiler/std/string.wast b/tests/compiler/std/string.wast index 12d9fd9e..4fca1051 100644 --- a/tests/compiler/std/string.wast +++ b/tests/compiler/std/string.wast @@ -3,11 +3,13 @@ (type $iii (func (param i32 i32) (result i32))) (type $iiii (func (param i32 i32 i32) (result i32))) (type $iiI (func (param i32 i32) (result i64))) + (type $iF (func (param i32) (result f64))) (type $v (func)) (global $std/string/str (mut i32) (i32.const 8)) (global $std:string/HEAD i32 (i32.const 4)) (global $std:string/CharCode.PLUS i32 (i32.const 43)) (global $std:string/CharCode.MINUS i32 (i32.const 45)) + (global $std:string/CharCode.DOT i32 (i32.const 46)) (global $std:string/CharCode._0 i32 (i32.const 48)) (global $std:string/CharCode._1 i32 (i32.const 49)) (global $std:string/CharCode._2 i32 (i32.const 50)) @@ -20,15 +22,17 @@ (global $std:string/CharCode._9 i32 (i32.const 57)) (global $std:string/CharCode.A i32 (i32.const 65)) (global $std:string/CharCode.B i32 (i32.const 66)) + (global $std:string/CharCode.E i32 (i32.const 69)) (global $std:string/CharCode.O i32 (i32.const 79)) (global $std:string/CharCode.X i32 (i32.const 88)) (global $std:string/CharCode.Z i32 (i32.const 90)) (global $std:string/CharCode.a i32 (i32.const 97)) (global $std:string/CharCode.b i32 (i32.const 98)) + (global $std:string/CharCode.e i32 (i32.const 101)) (global $std:string/CharCode.o i32 (i32.const 111)) (global $std:string/CharCode.x i32 (i32.const 120)) (global $std:string/CharCode.z i32 (i32.const 122)) - (global $HEAP_BASE i32 (i32.const 228)) + (global $HEAP_BASE i32 (i32.const 284)) (memory $0 1) (data (i32.const 8) "\10\00\00\00h\00i\00,\00 \00I\00\'\00m\00 \00a\00 \00s\00t\00r\00i\00n\00g\00") (data (i32.const 48) "\02\00\00\00h\00i\00") @@ -45,6 +49,9 @@ (data (i32.const 184) "\05\00\00\000\00x\00F\000\00F\00") (data (i32.const 200) "\03\00\00\000\001\001\00") (data (i32.const 216) "\04\00\00\000\00x\001\00g\00") + (data (i32.const 232) "\03\00\00\000\00.\001\00") + (data (i32.const 248) "\03\00\00\00.\002\005\00") + (data (i32.const 264) "\08\00\00\00.\001\00f\00o\00o\00b\00a\00r\00") (export "getString" (func $std/string/getString)) (export "memory" (memory $0)) (start $start) @@ -874,12 +881,7 @@ ) ) ) - (return - (i64.mul - (get_local $5) - (get_local $7) - ) - ) + (br $break|1) ) ) ) @@ -888,12 +890,7 @@ (get_local $4) (get_local $1) ) - (return - (i64.mul - (get_local $5) - (get_local $7) - ) - ) + (br $break|1) ) (set_local $7 (i64.add @@ -927,7 +924,288 @@ ) ) ) - (func $start (; 8 ;) (type $v) + (func $std:string/parseFloat (; 8 ;) (type $iF) (param $0 i32) (result f64) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 f64) + (local $5 f64) + (local $6 i32) + (local $7 f64) + (set_local $1 + (i32.load + (get_local $0) + ) + ) + (if + (i32.eqz + (get_local $1) + ) + (return + (f64.const nan:0x8000000000000) + ) + ) + (set_local $2 + (get_local $0) + ) + (set_local $3 + (i32.load16_u offset=4 + (get_local $2) + ) + ) + (nop) + (if + (i32.eq + (get_local $3) + (i32.const 45) + ) + (block + (if + (i32.eqz + (tee_local $1 + (i32.sub + (get_local $1) + (i32.const 1) + ) + ) + ) + (return + (f64.const nan:0x8000000000000) + ) + ) + (set_local $3 + (i32.load16_u offset=4 + (tee_local $2 + (i32.add + (get_local $2) + (i32.const 2) + ) + ) + ) + ) + (set_local $4 + (f64.neg + (f64.const 1) + ) + ) + ) + (if + (i32.eq + (get_local $3) + (i32.const 43) + ) + (block + (if + (i32.eqz + (tee_local $1 + (i32.sub + (get_local $1) + (i32.const 1) + ) + ) + ) + (return + (f64.const nan:0x8000000000000) + ) + ) + (set_local $3 + (i32.load16_u offset=4 + (tee_local $2 + (i32.add + (get_local $2) + (i32.const 2) + ) + ) + ) + ) + (set_local $4 + (f64.const 1) + ) + ) + (set_local $4 + (f64.const 1) + ) + ) + ) + (set_local $5 + (f64.const 0) + ) + (block $break|0 + (loop $continue|0 + (if + (block (result i32) + (set_local $6 + (get_local $1) + ) + (set_local $1 + (i32.sub + (get_local $6) + (i32.const 1) + ) + ) + (get_local $6) + ) + (block + (block + (set_local $3 + (i32.load16_u offset=4 + (get_local $2) + ) + ) + (if + (i32.eq + (get_local $3) + (i32.const 46) + ) + (block + (set_local $2 + (i32.add + (get_local $2) + (i32.const 2) + ) + ) + (set_local $7 + (f64.const 0.1) + ) + (block $break|1 + (loop $continue|1 + (if + (block (result i32) + (set_local $6 + (get_local $1) + ) + (set_local $1 + (i32.sub + (get_local $6) + (i32.const 1) + ) + ) + (get_local $6) + ) + (block + (block + (set_local $3 + (i32.load16_u offset=4 + (get_local $2) + ) + ) + (if + (i32.and + (if (result i32) + (i32.ne + (i32.eq + (get_local $3) + (i32.const 69) + ) + (i32.const 0) + ) + (i32.eq + (get_local $3) + (i32.const 69) + ) + (i32.eq + (get_local $3) + (i32.const 101) + ) + ) + (i32.const 1) + ) + (if + (i32.eqz + (i32.const 0) + ) + (unreachable) + ) + ) + (set_local $3 + (i32.sub + (get_local $3) + (i32.const 48) + ) + ) + (if + (i32.gt_u + (get_local $3) + (i32.const 9) + ) + (br $break|1) + ) + (set_local $5 + (f64.add + (get_local $5) + (f64.mul + (f64.convert_s/i32 + (get_local $3) + ) + (get_local $7) + ) + ) + ) + (set_local $7 + (f64.mul + (get_local $7) + (f64.const 0.1) + ) + ) + (set_local $2 + (i32.add + (get_local $2) + (i32.const 2) + ) + ) + ) + (br $continue|1) + ) + ) + ) + ) + (br $break|0) + ) + ) + (set_local $3 + (i32.sub + (get_local $3) + (i32.const 48) + ) + ) + (if + (i32.ge_u + (get_local $3) + (i32.const 10) + ) + (br $break|0) + ) + (set_local $5 + (f64.add + (f64.mul + (get_local $5) + (f64.const 10) + ) + (f64.convert_s/i32 + (get_local $3) + ) + ) + ) + (set_local $2 + (i32.add + (get_local $2) + (i32.const 2) + ) + ) + ) + (br $continue|0) + ) + ) + ) + ) + (return + (f64.mul + (get_local $4) + (get_local $5) + ) + ) + ) + (func $start (; 9 ;) (type $v) (if (i32.eqz (i32.eq @@ -1115,6 +1393,61 @@ ) (unreachable) ) + (if + (i32.eqz + (f64.eq + (call $std:string/parseFloat + (i32.const 120) + ) + (f64.const 0) + ) + ) + (unreachable) + ) + (if + (i32.eqz + (f64.eq + (call $std:string/parseFloat + (i32.const 128) + ) + (f64.const 1) + ) + ) + (unreachable) + ) + (if + (i32.eqz + (f64.eq + (call $std:string/parseFloat + (i32.const 232) + ) + (f64.const 0.1) + ) + ) + (unreachable) + ) + (if + (i32.eqz + (f64.eq + (call $std:string/parseFloat + (i32.const 248) + ) + (f64.const 0.25) + ) + ) + (unreachable) + ) + (if + (i32.eqz + (f64.eq + (call $std:string/parseFloat + (i32.const 264) + ) + (f64.const 0.1) + ) + ) + (unreachable) + ) ) ) (;