Skip to content

Commit 1941085

Browse files
WASM NEAR port initial commit
1 parent 871adc8 commit 1941085

29 files changed

+2998
-296
lines changed

.github/workflows/build.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,14 @@ jobs:
366366
with:
367367
config_hash: ${{ needs.build-context.outputs.config-hash }}
368368

369+
build-wasm-near:
370+
name: 'NEAR WASM'
371+
needs: build-context
372+
if: needs.build-context.outputs.run-tests == 'true'
373+
uses: ./.github/workflows/reusable-wasm-near.yml
374+
with:
375+
config_hash: ${{ needs.build-context.outputs.config-hash }}
376+
369377
test-hypothesis:
370378
name: "Hypothesis tests on Ubuntu"
371379
runs-on: ubuntu-24.04
@@ -617,6 +625,7 @@ jobs:
617625
- build-ubuntu
618626
- build-ubuntu-ssltests
619627
- build-wasi
628+
- build-wasm-near
620629
- test-hypothesis
621630
- build-asan
622631
- build-tsan
@@ -649,6 +658,7 @@ jobs:
649658
build-ubuntu,
650659
build-ubuntu-ssltests,
651660
build-wasi,
661+
build-wasm-near,
652662
test-hypothesis,
653663
build-asan,
654664
build-tsan,
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
name: Reusable NEAR WASM
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
config_hash:
7+
required: true
8+
type: string
9+
10+
env:
11+
FORCE_COLOR: 1
12+
13+
jobs:
14+
build-wasi-reusable:
15+
name: 'build and test'
16+
runs-on: ubuntu-24.04
17+
timeout-minutes: 60
18+
env:
19+
WASMTIME_VERSION: 22.0.0
20+
EMSCRIPTEN_VERSION: 4.0.10
21+
EMSDK_PATH: /opt/emsdk
22+
CROSS_BUILD_PYTHON: builddir/build
23+
CROSS_BUILD_WASI: builddir/emscripten-near
24+
steps:
25+
- uses: actions/checkout@v4
26+
with:
27+
persist-credentials: false
28+
- name: Cache Emscripten
29+
id: cache-emsdk
30+
uses: actions/cache@v4
31+
with:
32+
path: ${{ env.EMSDK_PATH }}
33+
key: ${{ runner.os }}-emsdk-${{ env.EMSCRIPTEN_VERSION }}
34+
restore-keys: |
35+
${{ runner.os }}-emsdk-${{ env.EMSCRIPTEN_VERSION }}
36+
- name: Install Emscripten
37+
if: steps.cache-emsdk.outputs.cache-hit != 'true'
38+
run: |
39+
git clone https://github.com/emscripten-core/emsdk.git ${{ env.EMSDK_PATH }}
40+
cd ${{ env.EMSDK_PATH }}
41+
./emsdk install 4.0.0
42+
./emsdk activate 4.0.0
43+
source ${{ env.EMSDK_PATH }}/emsdk_env.sh
44+
echo "$(dirname $(which emcc))" >> $GITHUB_PATH
45+
echo "EMSDK=${{ env.EMSDK_PATH }}" >> $GITHUB_ENV
46+
echo "EM_CONFIG=${{ env.EMSDK_PATH }}/.emscripten" >> $GITHUB_ENV
47+
echo "EMSDK_NODE=${{ env.EMSDK_PATH }}/node/18.20.3_64bit/bin/node" >> $GITHUB_ENV
48+
- name: "Configure ccache action"
49+
uses: hendrikmuhs/[email protected]
50+
with:
51+
save: ${{ github.event_name == 'push' }}
52+
max-size: "200M"
53+
- name: "Add ccache to PATH"
54+
run: echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV"
55+
- name: "Install Python"
56+
uses: actions/setup-python@v5
57+
with:
58+
python-version: '3.x'
59+
- name: "Runner image version"
60+
run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
61+
- name: "Restore Python build config.cache"
62+
uses: actions/cache@v4
63+
with:
64+
path: ${{ env.CROSS_BUILD_PYTHON }}/config.cache
65+
# Include env.pythonLocation in key to avoid changes in environment when setup-python updates Python.
66+
# Include the hash of `Tools/wasm/wasi.py` as it may change the environment variables.
67+
# (Make sure to keep the key in sync with the other config.cache step below.)
68+
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ env.EMSCRIPTEN_VERSION }}-${{ inputs.config_hash }}-${{ hashFiles('Tools/wasm/wasm_build.py') }}-${{ env.pythonLocation }}
69+
- name: "Configure build Python"
70+
run: python3 Tools/wasm/wasm_build.py emscripten-near configure-build-python -- --config-cache --with-pydebug
71+
- name: "Make build Python"
72+
run: python3 Tools/wasm/wasm_build.py emscripten-near make-build-python
73+
- name: "Restore host config.cache"
74+
uses: actions/cache@v4
75+
with:
76+
path: ${{ env.CROSS_BUILD_WASI }}/config.cache
77+
# Should be kept in sync with the other config.cache step above.
78+
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ env.EMSCRIPTEN_VERSION }}-${{ inputs.config_hash }}-${{ hashFiles('Tools/wasm/wasm_build.py') }}-${{ env.pythonLocation }}
79+
- name: "Configure host"
80+
# `--with-pydebug` inferred from configure-build-python
81+
run: python3 Tools/wasm/wasm_build.py emscripten-near configure-host -- --config-cache
82+
- name: "Make host"
83+
run: python3 Tools/wasm/wasm_build.py emscripten-near make-host
84+
- name: "Display build info"
85+
run: make --directory "${CROSS_BUILD_WASI}" pythoninfo
86+
- name: "Upload python.wasm"
87+
if: github.event_name == 'release'
88+
uses: actions/upload-release-asset@v1
89+
env:
90+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
91+
with:
92+
upload_url: ${{ github.event.release.upload_url }}
93+
asset_path: ${{ CROSS_BUILD_WASI }}/python.wasm
94+
asset_name: python.wasm
95+
asset_content_type: application/wasm
96+
- name: "Upload python-stdlib.zip"
97+
if: github.event_name == 'release'
98+
uses: actions/upload-release-asset@v1
99+
env:
100+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
101+
with:
102+
upload_url: ${{ github.event.release.upload_url }}
103+
asset_path: ${{ CROSS_BUILD_WASI }}/python-stdlib.zip
104+
asset_name: python-stdlib.zip
105+
asset_content_type: application/zip
106+
- name: "Test"
107+
run: make --directory "${CROSS_BUILD_WASI}" test

Lib/json/decoder.py

Lines changed: 31 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
"""Implementation of JSONDecoder
22
"""
3-
import re
43

54
from json import scanner
6-
try:
7-
from _json import scanstring as c_scanstring
8-
except ImportError:
9-
c_scanstring = None
5+
from _json import scanstring
106

117
__all__ = ['JSONDecoder', 'JSONDecodeError']
128

13-
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
14-
159
NaN = float('nan')
1610
PosInf = float('inf')
1711
NegInf = float('-inf')
@@ -50,89 +44,42 @@ def __reduce__(self):
5044
}
5145

5246

53-
HEXDIGITS = re.compile(r'[0-9A-Fa-f]{4}', FLAGS)
54-
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
5547
BACKSLASH = {
5648
'"': '"', '\\': '\\', '/': '/',
5749
'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t',
5850
}
5951

60-
def _decode_uXXXX(s, pos, _m=HEXDIGITS.match):
61-
esc = _m(s, pos + 1)
62-
if esc is not None:
63-
try:
64-
return int(esc.group(), 16)
65-
except ValueError:
66-
pass
67-
msg = "Invalid \\uXXXX escape"
68-
raise JSONDecodeError(msg, s, pos)
69-
70-
def py_scanstring(s, end, strict=True,
71-
_b=BACKSLASH, _m=STRINGCHUNK.match):
72-
"""Scan the string s for a JSON string. End is the index of the
73-
character in s after the quote that started the JSON string.
74-
Unescapes all valid JSON string escape sequences and raises ValueError
75-
on attempt to decode an invalid string. If strict is False then literal
76-
control characters are allowed in the string.
77-
78-
Returns a tuple of the decoded string and the index of the character in s
79-
after the end quote."""
80-
chunks = []
81-
_append = chunks.append
82-
begin = end - 1
83-
while 1:
84-
chunk = _m(s, end)
85-
if chunk is None:
86-
raise JSONDecodeError("Unterminated string starting at", s, begin)
87-
end = chunk.end()
88-
content, terminator = chunk.groups()
89-
# Content is contains zero or more unescaped string characters
90-
if content:
91-
_append(content)
92-
# Terminator is the end of string, a literal control character,
93-
# or a backslash denoting that an escape sequence follows
94-
if terminator == '"':
95-
break
96-
elif terminator != '\\':
97-
if strict:
98-
#msg = "Invalid control character %r at" % (terminator,)
99-
msg = "Invalid control character {0!r} at".format(terminator)
100-
raise JSONDecodeError(msg, s, end)
101-
else:
102-
_append(terminator)
103-
continue
104-
try:
105-
esc = s[end]
106-
except IndexError:
107-
raise JSONDecodeError("Unterminated string starting at",
108-
s, begin) from None
109-
# If not a unicode escape sequence, must be in the lookup table
110-
if esc != 'u':
111-
try:
112-
char = _b[esc]
113-
except KeyError:
114-
msg = "Invalid \\escape: {0!r}".format(esc)
115-
raise JSONDecodeError(msg, s, end)
116-
end += 1
117-
else:
118-
uni = _decode_uXXXX(s, end)
119-
end += 5
120-
if 0xd800 <= uni <= 0xdbff and s[end:end + 2] == '\\u':
121-
uni2 = _decode_uXXXX(s, end + 1)
122-
if 0xdc00 <= uni2 <= 0xdfff:
123-
uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
124-
end += 6
125-
char = chr(uni)
126-
_append(char)
127-
return ''.join(chunks), end
128-
129-
130-
# Use speedup if available
131-
scanstring = c_scanstring or py_scanstring
132-
133-
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
134-
WHITESPACE_STR = ' \t\n\r'
52+
class WhitespaceReplacer:
53+
"""Simple replacement for re.compile(r'[ \t\n\r]*', FLAGS) with same interface."""
54+
55+
def match(self, string, start=0, end=None):
56+
"""Match whitespace characters from start position."""
57+
if end is None:
58+
end = len(string)
13559

60+
pos = start
61+
while pos < end and string[pos] in ' \t\n\r':
62+
pos += 1
63+
64+
return WhitespaceMatch(start, pos)
65+
66+
class WhitespaceMatch:
67+
"""Match object returned by WhitespaceReplacer.match()."""
68+
69+
def __init__(self, start, end):
70+
self._start = start
71+
self._end = end
72+
73+
def end(self):
74+
"""Return the end position of the match."""
75+
return self._end
76+
77+
def start(self):
78+
"""Return the start position of the match."""
79+
return self._start
80+
81+
WHITESPACE = WhitespaceReplacer()
82+
WHITESPACE_STR = ' \t\n\r'
13683

13784
def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
13885
memo=None, _w=WHITESPACE.match, _ws=WHITESPACE_STR):

Lib/json/encoder.py

Lines changed: 6 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,12 @@
11
"""Implementation of JSONEncoder
22
"""
3-
import re
4-
5-
try:
6-
from _json import encode_basestring_ascii as c_encode_basestring_ascii
7-
except ImportError:
8-
c_encode_basestring_ascii = None
9-
try:
10-
from _json import encode_basestring as c_encode_basestring
11-
except ImportError:
12-
c_encode_basestring = None
13-
try:
14-
from _json import make_encoder as c_make_encoder
15-
except ImportError:
16-
c_make_encoder = None
17-
18-
ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
19-
ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
20-
HAS_UTF8 = re.compile(b'[\x80-\xff]')
21-
ESCAPE_DCT = {
22-
'\\': '\\\\',
23-
'"': '\\"',
24-
'\b': '\\b',
25-
'\f': '\\f',
26-
'\n': '\\n',
27-
'\r': '\\r',
28-
'\t': '\\t',
29-
}
30-
for i in range(0x20):
31-
ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
32-
#ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
33-
del i
343

35-
INFINITY = float('inf')
36-
37-
def py_encode_basestring(s):
38-
"""Return a JSON representation of a Python string
39-
40-
"""
41-
def replace(match):
42-
return ESCAPE_DCT[match.group(0)]
43-
return '"' + ESCAPE.sub(replace, s) + '"'
44-
45-
46-
encode_basestring = (c_encode_basestring or py_encode_basestring)
47-
48-
49-
def py_encode_basestring_ascii(s):
50-
"""Return an ASCII-only JSON representation of a Python string
51-
52-
"""
53-
def replace(match):
54-
s = match.group(0)
55-
try:
56-
return ESCAPE_DCT[s]
57-
except KeyError:
58-
n = ord(s)
59-
if n < 0x10000:
60-
return '\\u{0:04x}'.format(n)
61-
#return '\\u%04x' % (n,)
62-
else:
63-
# surrogate pair
64-
n -= 0x10000
65-
s1 = 0xd800 | ((n >> 10) & 0x3ff)
66-
s2 = 0xdc00 | (n & 0x3ff)
67-
return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
68-
return '"' + ESCAPE_ASCII.sub(replace, s) + '"'
4+
from _json import encode_basestring_ascii
5+
from _json import encode_basestring
6+
from _json import make_encoder
697

8+
INFINITY = float('inf')
709

71-
encode_basestring_ascii = (
72-
c_encode_basestring_ascii or py_encode_basestring_ascii)
7310

7411
class JSONEncoder(object):
7512
"""Extensible JSON <https://json.org> encoder for Python data structures.
@@ -248,8 +185,8 @@ def floatstr(o, allow_nan=self.allow_nan,
248185
indent = self.indent
249186
else:
250187
indent = ' ' * self.indent
251-
if _one_shot and c_make_encoder is not None:
252-
_iterencode = c_make_encoder(
188+
if _one_shot and make_encoder is not None:
189+
_iterencode = make_encoder(
253190
markers, self.default, _encoder, indent,
254191
self.key_separator, self.item_separator, self.sort_keys,
255192
self.skipkeys, self.allow_nan)

0 commit comments

Comments
 (0)