Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Leverage WASM_BIGINT for 64-bit integers #100

Merged
merged 2 commits into from
Dec 8, 2023
Merged
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
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,6 @@ npm test # ensure everything it's working fine

## Edge Cases

### Numbers

WASM does not support 64 bit integers only 32 bit integers and 64 bit doubles. If a number is an integer and will fit within a Lua integer then it will be pushed as a Lua native integer type. However, if it exceeds that size even if it is still an integer it will be pushed as a 64 bit double. This is unlikely to effect inteoperability with JS since JS treats all numbers the same but should allow some optimisation within Lua for smaller numbers.

### Null

`null` is not exposed to Lua and it has no awareness of it which can cause some issues when using it a table. `nil` is equivalent to `undefined`. Issue #39 tracks this and a workaround until `null` is added into Wasmoon.
Expand Down
15 changes: 1 addition & 14 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,6 @@ else
extension="-O3 --closure 1 -s ASSERTIONS=1"
fi

# Instead of telling Lua to be 32 bit for both floats and ints override the default
# int type to 32 bit and leave the float as 64 bits.
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -E -i '' "s/#define LUA_INT_DEFAULT\s+LUA_INT_LONGLONG/#define LUA_INT_DEFAULT \tLUA_INT_INT/" ./lua/luaconf.h
else
sed -E -i "s/#define LUA_INT_DEFAULT\s+LUA_INT_LONGLONG/#define LUA_INT_DEFAULT \tLUA_INT_INT/" ./lua/luaconf.h
fi

emcc \
-s WASM=1 $extension -o ./build/glue.js \
-s EXPORTED_RUNTIME_METHODS="[
Expand Down Expand Up @@ -51,6 +43,7 @@ emcc \
-s NODEJS_CATCH_REJECTION=0 \
-s MALLOC=emmalloc \
-s STACK_SIZE=1MB \
-s WASM_BIGINT \
-s EXPORTED_FUNCTIONS="[
'_malloc', \
'_free', \
Expand Down Expand Up @@ -205,9 +198,3 @@ emcc \
'_luaL_openlibs' \
]" \
${LUA_SRC}

if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "s/^#define LUA_32BITS\t1$/#define LUA_32BITS\t0/" ./lua/luaconf.h
else
sed -i "s/^#define LUA_32BITS\t1$/#define LUA_32BITS\t0/" ./lua/luaconf.h
fi
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@
"@types/node": "20.8.9",
"@typescript-eslint/eslint-plugin": "6.9.0",
"@typescript-eslint/parser": "6.9.0",
"mocha": "10.2.0",
"jest-mock": "29.7.0",
"chai": "4.3.10",
"chai-as-promised": "7.1.1",
"eslint": "8.52.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-prettier": "5.0.1",
"eslint-plugin-sort-imports-es6-autofix": "0.6.0",
"fengari": "0.1.4",
"jest-mock": "29.7.0",
"mocha": "10.2.0",
"prettier": "3.0.3",
"rollup": "4.1.5",
"rollup-plugin-copy": "3.5.0",
Expand Down
12 changes: 6 additions & 6 deletions src/luawasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export default class LuaWasm {
public lua_type: (L: LuaState, idx: number) => LuaType
public lua_typename: (L: LuaState, tp: number) => string
public lua_tonumberx: (L: LuaState, idx: number, isnum: number | null) => number
public lua_tointegerx: (L: LuaState, idx: number, isnum: number | null) => number
public lua_tointegerx: (L: LuaState, idx: number, isnum: number | null) => bigint
public lua_toboolean: (L: LuaState, idx: number) => number
public lua_tolstring: (L: LuaState, idx: number, len: number | null) => string
public lua_rawlen: (L: LuaState, idx: number) => number
Expand All @@ -121,7 +121,7 @@ export default class LuaWasm {
public lua_compare: (L: LuaState, idx1: number, idx2: number, op: number) => number
public lua_pushnil: (L: LuaState) => void
public lua_pushnumber: (L: LuaState, n: number) => void
public lua_pushinteger: (L: LuaState, n: number) => void
public lua_pushinteger: (L: LuaState, n: bigint) => void
public lua_pushlstring: (L: LuaState, s: string | number | null, len: number) => string
public lua_pushstring: (L: LuaState, s: string | number | null) => string
public lua_pushcclosure: (L: LuaState, fn: number, n: number) => void
Expand All @@ -131,9 +131,9 @@ export default class LuaWasm {
public lua_getglobal: (L: LuaState, name: string | null) => LuaType
public lua_gettable: (L: LuaState, idx: number) => LuaType
public lua_getfield: (L: LuaState, idx: number, k: string | null) => LuaType
public lua_geti: (L: LuaState, idx: number, n: number) => LuaType
public lua_geti: (L: LuaState, idx: number, n: bigint) => LuaType
public lua_rawget: (L: LuaState, idx: number) => number
public lua_rawgeti: (L: LuaState, idx: number, n: number) => LuaType
public lua_rawgeti: (L: LuaState, idx: number, n: bigint) => LuaType
public lua_rawgetp: (L: LuaState, idx: number, p: number | null) => LuaType
public lua_createtable: (L: LuaState, narr: number, nrec: number) => void
public lua_newuserdatauv: (L: LuaState, sz: number, nuvalue: number) => number
Expand All @@ -142,9 +142,9 @@ export default class LuaWasm {
public lua_setglobal: (L: LuaState, name: string | null) => void
public lua_settable: (L: LuaState, idx: number) => void
public lua_setfield: (L: LuaState, idx: number, k: string | null) => void
public lua_seti: (L: LuaState, idx: number, n: number) => void
public lua_seti: (L: LuaState, idx: number, n: bigint) => void
public lua_rawset: (L: LuaState, idx: number) => void
public lua_rawseti: (L: LuaState, idx: number, n: number) => void
public lua_rawseti: (L: LuaState, idx: number, n: bigint) => void
public lua_rawsetp: (L: LuaState, idx: number, p: number | null) => void
public lua_setmetatable: (L: LuaState, objindex: number) => number
public lua_setiuservalue: (L: LuaState, idx: number, n: number) => number
Expand Down
9 changes: 2 additions & 7 deletions src/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ export interface OrderedExtension {

// When the debug count hook is set, call it every X instructions.
const INSTRUCTION_HOOK_COUNT = 1000
// These reflect math.maxinteger and math.mininteger with 32 bit ints in Lua.
const MAX_SAFE_INTEGER = Math.pow(2, 31) - 1
const MIN_SAFE_INTEGER = -Math.pow(2, 31)

export default class Thread {
public readonly address: LuaState
Expand Down Expand Up @@ -207,10 +204,8 @@ export default class Thread {
this.lua.lua_pushnil(this.address)
break
case 'number':
// Only push it as an integer if it fits within 32 bits, otherwise treat it as a double.
// This is because wasm doesn't support 32 bit ints but does support 64 bit floats.
if (Number.isInteger(target) && target <= MAX_SAFE_INTEGER && target >= MIN_SAFE_INTEGER) {
this.lua.lua_pushinteger(this.address, target)
if (Number.isInteger(target)) {
this.lua.lua_pushinteger(this.address, BigInt(target))
} else {
this.lua.lua_pushnumber(this.address, target)
}
Expand Down
2 changes: 1 addition & 1 deletion src/type-extensions/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ class FunctionTypeExtension extends TypeExtension<FunctionType, FunctionDecorati
return
}

const internalType = thread.lua.lua_rawgeti(thread.address, LUA_REGISTRYINDEX, func)
const internalType = thread.lua.lua_rawgeti(thread.address, LUA_REGISTRYINDEX, BigInt(func))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conversion seems odd. Should luaL_ref now return bigint?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does seem a bit strange, but this matches the lua function definitions:

LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n);
// ...
LUALIB_API int (luaL_ref) (lua_State *L, int t);

if (internalType !== LuaType.Function) {
const callMetafieldType = thread.lua.luaL_getmetafield(thread.address, -1, '__call')
thread.pop()
Expand Down
4 changes: 2 additions & 2 deletions src/type-extensions/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class TableTypeExtension extends TypeExtension<TableType> {
const seenMap = userdata || new Map<any, number>()
const existingReference = seenMap.get(target)
if (existingReference !== undefined) {
thread.lua.lua_rawgeti(thread.address, LUA_REGISTRYINDEX, existingReference)
thread.lua.lua_rawgeti(thread.address, LUA_REGISTRYINDEX, BigInt(existingReference))
return true
}

Expand All @@ -58,7 +58,7 @@ class TableTypeExtension extends TypeExtension<TableType> {
thread.lua.lua_createtable(thread.address, arrayCount, keyCount)
const ref = thread.lua.luaL_ref(thread.address, LUA_REGISTRYINDEX)
seenMap.set(target, ref)
thread.lua.lua_rawgeti(thread.address, LUA_REGISTRYINDEX, ref)
thread.lua.lua_rawgeti(thread.address, LUA_REGISTRYINDEX, BigInt(ref))
}

if (Array.isArray(target)) {
Expand Down
17 changes: 7 additions & 10 deletions test/engine.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -686,8 +686,7 @@ describe('Engine', () => {

const res = await engine.doString(`return tostring(value)`)

// Since it's a float in Lua it will be prefixed with .0 from tostring()
expect(res).to.be.equal(`${String(value)}.0`)
expect(res).to.be.equal(`${String(value)}`)
})

it('number greater than 32 bit int should be pushed and retrieved as number', async () => {
Expand All @@ -700,15 +699,13 @@ describe('Engine', () => {
expect(res).to.be.equal(value)
})

it('print max integer as 32 bits', async () => {
it('number greater than 32 bit int should be usable as a format argument', async () => {
const engine = await getEngine()
const res = await engine.doString(`return math.maxinteger`)
expect(res).to.be.equal(2147483647)
})
const value = 1689031554550
engine.global.set('value', value)

it('print min integer as 32 bits', async () => {
const engine = await getEngine()
const res = await engine.doString(`return math.mininteger`)
expect(res).to.be.equal(-2147483648)
const res = await engine.doString(`return ("%d"):format(value)`)

expect(res).to.be.equal('1689031554550')
})
})