Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions DevNotes.md
Original file line number Diff line number Diff line change
@@ -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

```
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
33 changes: 29 additions & 4 deletions src/AstToWasm.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) =

Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/FSharpTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ let FS_INT32 = "Microsoft.FSharp.Core.int32"
[<Literal>]
let FS_INT = "Microsoft.FSharp.Core.int"

[<Literal>]
let FS_UINT32 = "Microsoft.FSharp.Core.uint32"

// [<Literal>]
// let FS_UINT = "Microsoft.FSharp.Core.uint"

[<Literal>]
let FS_OPERATOR = "Microsoft.FSharp.Core.Operators"

Expand Down Expand Up @@ -47,5 +53,6 @@ let FS_OP_LESSTHANOREQUAL = "op_LessThanOrEqual"

type Types =
| Int32
| UInt32
| Unit
| Any
5 changes: 5 additions & 0 deletions src/Utils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
50 changes: 48 additions & 2 deletions test/AstToWasmTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -305,7 +306,7 @@ let main () = 0
response.Should().Be(expected)

[<Fact>]
let ``Can support invalid F# syntax throw an error`` () =
let ``Can throw when invalid F# syntax`` () =
let input =
$"""module Test

Expand Down Expand Up @@ -387,3 +388,48 @@ let main () = 0

let response = wasmBytes |> runInt32FuncInt32 "countTo" 50
response.Should().Be(63)

[<Theory>]
[<InlineData("0u", 0)>]
[<InlineData("2147483647u", Int32.MaxValue)>]
[<InlineData("1u + 3u", 4)>]
[<InlineData("1u + 3u + 2u", 6)>]
[<InlineData("1u - 3u", -2)>]
[<InlineData("10u / 2u", 5)>]
[<InlineData("11u / 2u", 5)>]
[<InlineData("14u / 5u", 2)>]
[<InlineData("11u % 2u", 1)>]
[<InlineData("14u % 5u", 4)>]
[<InlineData("10u * 15u", 150)>]
[<InlineData("10u * 15u + 10u", 160)>]
[<InlineData("10u * (15u + 10u)", 250)>]
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)

[<Fact>]
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<System.Exception, _>()
8 changes: 8 additions & 0 deletions test/Helpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@ let runInt32FuncInt32 (funcName: string) (param1: int32) (wasmBytes: byte list)

let func = instance.GetFunction<int32, int32>(funcName)
func.Invoke(param1)

let runFuncUint32Return (funcName: string) (wasmBytes: byte list) =
let instance = buildInstance wasmBytes

let functions = instance.GetFunctions()

let func = instance.GetFunction<uint32>(funcName)
func.Invoke()
115 changes: 115 additions & 0 deletions test/Leb128Tests.fs
Original file line number Diff line number Diff line change
@@ -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 |]
| _ -> [||]

[<Theory>]
[<InlineData(2u, 1)>]
[<InlineData(64u, 1)>]
[<InlineData(127u, 1)>]
[<InlineData(128u, 2)>]
[<InlineData(16383u, 2)>]
[<InlineData(16384u, 3)>]
[<InlineData(283828u, 3)>]
[<InlineData(4_294_967_295u, 5)>]
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")
| _ -> [||]

[<Theory>]
[<InlineData(1)>]
[<InlineData(-1)>]
[<InlineData(63)>]
[<InlineData(64)>]
[<InlineData(-64)>]
[<InlineData(-65)>]
[<InlineData(127)>]
[<InlineData(128)>]
[<InlineData(-128)>]
[<InlineData(-129)>]
[<InlineData(7196)>]
[<InlineData(8192)>]
[<InlineData(-7196)>]
[<InlineData(-8193)>]
[<InlineData(283828)>]
[<InlineData(2_147_483_647)>]
[<InlineData(-283828)>]
[<InlineData(-2_147_483_648)>]
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])
1 change: 1 addition & 0 deletions test/TinyFS.Test.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<ItemGroup>
<Compile Include="Helpers.fs" />
<Compile Include="Leb128Tests.fs" />
<Compile Include="FSharpToAstTests.fs" />
<Compile Include="AstToWasmTests.fs" />
<Compile Include="Program.fs" />
Expand Down