diff --git a/DevNotes.md b/DevNotes.md index 7e6bbd1..507f44d 100644 --- a/DevNotes.md +++ b/DevNotes.md @@ -1,5 +1,11 @@ # Developer Notes +## Tagging a Release + +``` +git tag -a v0.0.1 -m "First release of TinyFS" +git push origin tag v0.0.1 +``` ## Error ``` diff --git a/README.md b/README.md index 2f19e29..814714b 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Below lists the following language features that are supported. - [ ] `int16` - [ ] `uint16` - [x] `int/int32` -- [ ] `uint/uint32` +- [ ] `uint/uint32` † - [ ] `long/int64` - [ ] `ulong/uint64` - [ ] `nativeint` - probably never @@ -70,7 +70,7 @@ Below lists the following language features that are supported. * `int64`, `float32`, `float64` and `bool` * WebAssembly provides builtin support for `int32`, `int64`, `float32` and `float64`. Everything else comes extra. - +† (WebAssembly's 32 bit integer cannot display values larger (2 ^ 31 - 1)). So a `uint32` and `int32` max values are the same. ### Language Constructs - [x] `let` bindings diff --git a/src/AstToWasm.fs b/src/AstToWasm.fs index be9714d..6eab460 100644 --- a/src/AstToWasm.fs +++ b/src/AstToWasm.fs @@ -368,8 +368,13 @@ let getType (strType) = | FS_INT | FS_INT32 -> Types.Int32 | FS_UNIT -> Types.Unit + | FS_UINT32 -> Types.UInt32 | _ -> failwith (sprintf "TinyFS: %s is an unknown type" strType) +let getTypeFromFSharpType (typ: FSharpType) = getType typ.BasicQualifiedName + +let getTypeFromFSharpExpr (expr: FSharpExpr) = getType expr.Type.BasicQualifiedName + ///Start converting functions let operatorToWasm (op: string) (typ: Types) = match op, typ with @@ -479,14 +484,27 @@ let rec exprToWasm wasmBytes | FSharpExprPatterns.Const (value, typ) -> - let typ = getType typ.BasicQualifiedName + let wType = getType typ.BasicQualifiedName - match typ with + match wType with | Types.Int32 -> match convertInt value with | Some vall -> appendSinList i32_CONST (i32 vall) | None -> failwith (sprintf "TinyFS: Cannot convert '%s' to Int32" (value.ToString())) - | _ -> failwith (sprintf "TinyFS: Cannot extract value from %s type" (typ.ToString())) + | Types.UInt32 -> + match convertUInt value with + | Some vall -> + if vall > (uint32) Int32.MaxValue then + failwith ( + sprintf + "TinyFS: %d is a larger uint32 value than WebAssembly's max of %d" + vall + ((uint32) Int32.MaxValue) + ) + else + appendSinList i32_CONST (u32 vall) + | None -> failwith (sprintf "TinyFS: Cannot convert '%s' to UInt32" (value.ToString())) + | _ -> failwith (sprintf "TinyFS: Cannot extract value from %s type" (wType.ToString())) | FSharpExprPatterns.Let ((vall, letExpr, _), rightExpr) -> let leftWasm = exprToWasm letExpr moduleSymbols functionSymbols let rightWasm = exprToWasm rightExpr moduleSymbols functionSymbols @@ -552,6 +570,7 @@ let rec defineFunctionDecls decls (moduleSymbols: ModuleSymbolDict) : WasmFuncBy //if that a zero parameter function? Or is that a module wide variable?? //does it matter?? let name = vall.CompiledName + let bodyType = getTypeFromFSharpExpr body let (functionSymbols, idx) = @@ -574,13 +593,19 @@ let rec defineFunctionDecls decls (moduleSymbols: ModuleSymbolDict) : WasmFuncBy |> Seq.sortBy (fun sym -> sym.index) |> Seq.length + let locals = + if varsCount > 0 then + [ locals varsCount i32_VAL_TYPE ] + else + [] + let bodyWasm = exprToWasm body moduleSymbols functionSymbols let functionDecls: WasmFuncBytes list = [ { name = name paramTypes = paramTypes resultType = i32_VAL_TYPE - localBytes = [ locals varsCount i32_VAL_TYPE ] + localBytes = locals body = appendListSin bodyWasm INSTR_END } ] functionDecls diff --git a/src/FSharpTypes.fs b/src/FSharpTypes.fs index d5e13ea..d8aedf0 100644 --- a/src/FSharpTypes.fs +++ b/src/FSharpTypes.fs @@ -9,6 +9,12 @@ let FS_INT32 = "Microsoft.FSharp.Core.int32" [] let FS_INT = "Microsoft.FSharp.Core.int" +[] +let FS_UINT32 = "Microsoft.FSharp.Core.uint32" + +// [] +// let FS_UINT = "Microsoft.FSharp.Core.uint" + [] let FS_OPERATOR = "Microsoft.FSharp.Core.Operators" @@ -47,5 +53,6 @@ let FS_OP_LESSTHANOREQUAL = "op_LessThanOrEqual" type Types = | Int32 + | UInt32 | Unit | Any diff --git a/src/Utils.fs b/src/Utils.fs index dbcf2ac..9568026 100644 --- a/src/Utils.fs +++ b/src/Utils.fs @@ -7,6 +7,11 @@ let convertInt (o: obj) = | true, int -> Some int | _ -> None +let convertUInt (o: obj) = + match System.UInt32.TryParse(o.ToString()) with + | true, uint -> Some uint + | _ -> None + let determineExprPattern (expr) = match expr with | FSharpExprPatterns.AddressOf (a1) -> "AddressOf" diff --git a/test/AstToWasmTests.fs b/test/AstToWasmTests.fs index cb5e325..7711cab 100644 --- a/test/AstToWasmTests.fs +++ b/test/AstToWasmTests.fs @@ -3,12 +3,13 @@ open Faqt open Faqt.AssertionHelpers open Faqt.Operators +open FSharp.Compiler.CodeAnalysis open Helpers +open System open TinyFS.Core.AstToWasm open TinyFS.Core.FSharpToAst open TinyFS.Core.FSharpTypes open Xunit -open FSharp.Compiler.CodeAnalysis let checker: FSharpChecker = FSharpChecker.Create(keepAssemblyContents = true) @@ -305,7 +306,7 @@ let main () = 0 response.Should().Be(expected) [] -let ``Can support invalid F# syntax throw an error`` () = +let ``Can throw when invalid F# syntax`` () = let input = $"""module Test @@ -387,3 +388,48 @@ let main () = 0 let response = wasmBytes |> runInt32FuncInt32 "countTo" 50 response.Should().Be(63) + +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +let ``Can compile and run uint32 wasm expressions`` expr expected = + let input = + $""" +module Test + +let main () = {expr} +""" + + let declarations = getDeclarations checker input + let wasmBytes = astToWasm declarations + + //printWasm wasmBytes + + let response = wasmBytes |> runFuncInt32Return "main" + response.Should().Be(expected) + +[] +let ``Can throw when unsigned number is too large`` () = + let input = + $""" +module Test + +let main () = 2147483648u +""" + + let declarations = getDeclarations checker input + + (fun () -> astToWasm declarations) + .Should() + .Throw() diff --git a/test/Helpers.fs b/test/Helpers.fs index 259f619..a795860 100644 --- a/test/Helpers.fs +++ b/test/Helpers.fs @@ -33,3 +33,11 @@ let runInt32FuncInt32 (funcName: string) (param1: int32) (wasmBytes: byte list) let func = instance.GetFunction(funcName) func.Invoke(param1) + +let runFuncUint32Return (funcName: string) (wasmBytes: byte list) = + let instance = buildInstance wasmBytes + + let functions = instance.GetFunctions() + + let func = instance.GetFunction(funcName) + func.Invoke() diff --git a/test/Leb128Tests.fs b/test/Leb128Tests.fs new file mode 100644 index 0000000..d4f761f --- /dev/null +++ b/test/Leb128Tests.fs @@ -0,0 +1,115 @@ +module TinyFS.Test.Leb128Tests + +open TinyFS.Core.AstToWasm +open Xunit + +let getUnsignedExpectedBytes (num: uint32) = + match num with + | 2u -> [| 2uy |] + | 64u -> [| 64uy |] + | 127u -> [| 127uy |] + | 128u -> [| 128uy; 1uy |] + | 16383u -> [| 255uy; 127uy |] + | 16384u -> [| 128uy; 128uy; 1uy |] + | 283828u -> [| 180uy; 169uy; 17uy |] + | 4_294_967_295u -> [| 255uy; 255uy; 255uy; 255uy; 15uy |] + | _ -> [||] + +[] +[] +[] +[] +[] +[] +[] +[] +[] +let ``Can encode unsigned integer via LEB128`` num expectedLength = + let lebEncoded = u32 num + + Assert.Equal(expectedLength, lebEncoded.Length) + + let expectedBytes = getUnsignedExpectedBytes num + + if expectedLength = 5 then + Assert.Equal(expectedBytes[4], lebEncoded[4]) + Assert.Equal(expectedBytes[3], lebEncoded[3]) + Assert.Equal(expectedBytes[2], lebEncoded[2]) + Assert.Equal(expectedBytes[1], lebEncoded[1]) + Assert.Equal(expectedBytes[0], lebEncoded[0]) + elif expectedLength = 3 then + Assert.Equal(expectedBytes[2], lebEncoded[2]) + Assert.Equal(expectedBytes[1], lebEncoded[1]) + Assert.Equal(expectedBytes[0], lebEncoded[0]) + + elif expectedLength = 2 then + Assert.Equal(expectedBytes[1], lebEncoded[1]) + Assert.Equal(expectedBytes[0], lebEncoded[0]) + else + Assert.Equal(expectedBytes[0], lebEncoded[0]) + +let getSignedExpectedBytes (num: int32) = + match num with + | 1 -> [| 1uy |] + | -1 -> [| 127uy |] + | 63 -> [| 63uy |] + | 64 -> [| 192uy; 0uy |] + | -64 -> [| 64uy |] + | -65 -> [| 191uy; 127uy |] + | 127 -> [| 255uy; 0uy |] + | 128 -> [| 128uy; 1uy |] + | -128 -> [| 128uy; 127uy |] + | -129 -> [| 255uy; 126uy |] + | 7196 -> [| 156uy; 56uy |] + | 8192 -> [| 128uy; 192uy; 0uy |] + | -7196 -> [| 228uy; 71uy |] + | -8193 -> [| 255uy; 191uy; 127uy |] + | 283828 -> System.Convert.FromHexString("B4A911") + | 2_147_483_647 -> System.Convert.FromHexString("FFFFFFFF07") + | -283828 -> System.Convert.FromHexString("CCD66E") + | -2_147_483_648 -> System.Convert.FromHexString("8080808078") + | _ -> [||] + +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +let ``Can encode signed integer via LEB128`` num = + let lebEncoded = i32 num + let expectedBytes = getSignedExpectedBytes num + + Assert.Equal(expectedBytes.Length, lebEncoded.Length) + + let expectedLength = expectedBytes.Length + + if expectedLength = 5 then + Assert.Equal(expectedBytes[4], lebEncoded[4]) + Assert.Equal(expectedBytes[3], lebEncoded[3]) + Assert.Equal(expectedBytes[2], lebEncoded[2]) + Assert.Equal(expectedBytes[1], lebEncoded[1]) + Assert.Equal(expectedBytes[0], lebEncoded[0]) + elif expectedLength = 3 then + Assert.Equal(expectedBytes[2], lebEncoded[2]) + Assert.Equal(expectedBytes[1], lebEncoded[1]) + Assert.Equal(expectedBytes[0], lebEncoded[0]) + + elif expectedLength = 2 then + Assert.Equal(expectedBytes[1], lebEncoded[1]) + Assert.Equal(expectedBytes[0], lebEncoded[0]) + else + Assert.Equal(expectedBytes[0], lebEncoded[0]) diff --git a/test/TinyFS.Test.fsproj b/test/TinyFS.Test.fsproj index c39d9fd..4fa6e6c 100644 --- a/test/TinyFS.Test.fsproj +++ b/test/TinyFS.Test.fsproj @@ -8,6 +8,7 @@ +