Skip to content

Commit

Permalink
feat: add string ffi (UCS2) (#56)
Browse files Browse the repository at this point in the history
* feat: add string ffi UCS2

* chore: add todo
  • Loading branch information
menduz authored Dec 20, 2019
1 parent 297338c commit b45df7a
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 9 deletions.
8 changes: 7 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,10 @@ grammar changes:
16u32
```

porque si dallta checkeo de tipos sigue de largo para ExecutionHelper#testSrc?
porque si dallta checkeo de tipos sigue de largo para ExecutionHelper#testSrc?


```lys
fun externalEat(): UnsafeCPointer = eat() // fails horribly with wasm validation errors
fun externalEat(): UnsafeCPointer = eat() as UnsafeCPointer // doesn't fail
```
98 changes: 98 additions & 0 deletions examples/tests/string-ffi.spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Test

Ensure basics of functionality of strings ffi.

#### main.lys

```lys
import support::ffi
#[export] fun validateEmptyString(str: UnsafeCPointer): boolean = {
val received = UCS2.fromPtr(str)
received == ""
}
#[export] fun validateNonEmptyString(str: UnsafeCPointer): boolean = {
val received = UCS2.fromPtr(str)
received == "TesT!"
}
#[export] fun getEmptyString(): UnsafeCPointer = {
"" as UnsafeCPointer
}
#[export] fun getNonEmptyString(): UnsafeCPointer = {
"Agus 🚢 💫 ڞ" as UnsafeCPointer
}
#[export] fun identity(str: UnsafeCPointer): UnsafeCPointer = {
UCS2.fromPtr(str) as UnsafeCPointer
}
#[export] fun main(): void = {
// noop
}
```

#### assertions.js

```js
getInstance => {
const ffi = require('./execution')
const instance = getInstance()
const exports = instance.exports

const errors = []

function write(str) {
return ffi.writeStringToHeap(instance, str)
}

function read(ptr) {
const str = ffi.readStringFromHeap(instance, ptr)
console.log("Read: " + str)
return str
}

if (!exports.validateEmptyString(write(""))) {
errors.push('validateEmptyString1')
}

if (!exports.validateNonEmptyString(write("TesT!"))) {
errors.push('validateNonEmptyString1')
}

if (!exports.validateEmptyString(write(""))) {
errors.push('validateEmptyString2')
}

if (!exports.validateNonEmptyString(write("TesT!"))) {
errors.push('validateNonEmptyString2')
}

if (read(exports.getEmptyString()) != "") {
errors.push('getEmptyString')
}

if (read(exports.getNonEmptyString()) != "Agus 🚢 💫 ڞ") {
errors.push('getNonEmptyString')
}

['', "a", "1234", "", "🥶"].forEach(t => {
if (read(write(t)) != t) {
errors.push(`read(write(${t}))`)
}
})

// ['', "a", "1234", "௸", "🥶"].forEach(t => {
// if (read(exports.identity(write(t))) != t) {
// errors.push(`identity ${t}`)
// }
// })

if (errors.length) {
throw new Error(errors.join(', '))
}
};
```
36 changes: 28 additions & 8 deletions src/utils/execution.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @lys-require=support::ffi

export function readString(memory: ArrayBuffer, offset: number) {
const dv = new DataView(memory, offset);
let len = dv.getUint32(0, true);
Expand All @@ -21,24 +23,42 @@ export function readString(memory: ArrayBuffer, offset: number) {
}

export function writeStringToHeap(instance: any, value: string) {
const allocatedString = instance.exports.malloc(4 + value.length);
const allocatedString = instance.exports.ffi_allocateString(value.length);

// UCS16
// UCS2
const dv = new DataView(instance.exports.memory.buffer, allocatedString);
let len = value.length;
dv.setUint32(0, len * 2, true);
const len = value.length;

let i = 0;
while (i < len) {
dv.setUint16(i * 2, value.charCodeAt(i), true);
i++;
}

return allocatedString;
}

export function readStringFromHeap(instance: any, unsafeCPointer: number): string {
const dv = new DataView(instance.exports.memory.buffer, unsafeCPointer - 4);
let len = dv.getUint32(0, true);

if (len === 0) {
return '';
}

let currentOffset = 4;
len += 4;

let i = 0;
const sb: string[] = [];

while (currentOffset < len) {
dv.setUint16(currentOffset, value.charCodeAt(i), true);
const r = dv.getUint16(currentOffset, true);
if (r === 0) break; // Break if we find a null character
sb.push(String.fromCharCode(r));
currentOffset += 2;
i++;
}

return allocatedString;
return sb.join('');
}

export function readBytes(memory: ArrayBuffer, offset: number) {
Expand Down
27 changes: 27 additions & 0 deletions stdlib/support/ffi.lys
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
private fun stringDiscriminant(): u64 = {
val discriminant: u32 = string.^discriminant
discriminant as u64 << 32
}

#[export "ffi_allocateString"]
private fun allocateString(sizeInChars: u32): UnsafeCPointer = {
string.fromBytes(bytes(sizeInChars << 1)) as UnsafeCPointer
}

type UCS2 = void

impl UCS2 {
// Converts a pointer resulted from a allocBytes to a string
fun fromPtr(bytesPointer: UnsafeCPointer): string = %wasm {
(i64.or
(call $stringDiscriminant)
(i64.and
(i64.const 0xFFFFFFFF)
(i64.extend_u/i32
(i32.sub
(get_local $bytesPointer)
(i32.const 4)))))
}

fun toPtr(str: string): UnsafeCPointer = str as UnsafeCPointer
}

0 comments on commit b45df7a

Please sign in to comment.